├── .eslintignore ├── test ├── conf │ ├── redis.conf │ ├── password.conf │ ├── slave.conf │ ├── rename.conf │ ├── stunnel.conf.template │ ├── faulty.cert │ ├── redis.js.org.cert │ └── redis.js.org.key ├── lib │ ├── unref.js │ ├── good-traces.js │ ├── config.js │ ├── stunnel-process.js │ └── redis-process.js ├── commands │ ├── scard.spec.js │ ├── zscore.spec.js │ ├── sismember.spec.js │ ├── randomkey.test.js │ ├── rpush.spec.js │ ├── ttl.spec.js │ ├── geoadd.spec.js │ ├── setex.spec.js │ ├── spop.spec.js │ ├── setnx.spec.js │ ├── hincrby.spec.js │ ├── hlen.spec.js │ ├── smembers.spec.js │ ├── exists.spec.js │ ├── rename.spec.js │ ├── msetnx.spec.js │ ├── smove.spec.js │ ├── expire.spec.js │ ├── slowlog.spec.js │ ├── renamenx.spec.js │ ├── sdiff.spec.js │ ├── sunion.spec.js │ ├── sdiffstore.spec.js │ ├── sinterstore.spec.js │ ├── zscan.spec.js │ ├── sunionstore.spec.js │ ├── watch.spec.js │ ├── zadd.spec.js │ ├── script.spec.js │ ├── type.spec.js │ ├── del.spec.js │ ├── sinter.spec.js │ ├── sadd.spec.js │ ├── keys.spec.js │ ├── srem.spec.js │ ├── hmget.spec.js │ ├── info.spec.js │ ├── blpop.spec.js │ ├── mget.spec.js │ ├── get.spec.js │ ├── dbsize.spec.js │ ├── hgetall.spec.js │ ├── hset.spec.js │ ├── incr.spec.js │ ├── getset.spec.js │ ├── flushdb.spec.js │ ├── mset.spec.js │ ├── hmset.spec.js │ ├── sort.spec.js │ └── select.spec.js ├── good_traces.spec.js ├── conect.slave.spec.js ├── custom_errors.spec.js ├── prefix.spec.js ├── rename.spec.js └── tls.spec.js ├── .gitignore ├── .npmignore ├── examples ├── auth.js ├── mget.js ├── monitor.js ├── eval.js ├── sort.js ├── subqueries.js ├── subquery.js ├── extend.js ├── unix_socket.js ├── simple.js ├── backpressure_drain.js ├── multi2.js ├── psubscribe.js ├── streams.js ├── web_server.js ├── multi.js ├── pub_sub.js ├── file.js └── scan.js ├── lib ├── debug.js ├── command.js ├── customErrors.js ├── createClient.js ├── extendedApi.js ├── utils.js └── commands.js ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .travis.yml ├── LICENSE ├── appveyor.yml ├── package.json ├── .eslintrc └── benchmarks └── diff_multi_bench_output.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | coverage/** 3 | **.md 4 | **.log 5 | -------------------------------------------------------------------------------- /test/conf/redis.conf: -------------------------------------------------------------------------------- 1 | port 6379 2 | bind ::1 127.0.0.1 3 | unixsocket /tmp/redis.sock 4 | unixsocketperm 700 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tern-port 3 | .nyc_output 4 | coverage 5 | *.log 6 | *.rdb 7 | stunnel.conf 8 | stunnel.pid 9 | *.out 10 | -------------------------------------------------------------------------------- /test/conf/password.conf: -------------------------------------------------------------------------------- 1 | requirepass porkchopsandwiches 2 | port 6379 3 | bind ::1 127.0.0.1 4 | unixsocket /tmp/redis.sock 5 | unixsocketperm 700 6 | -------------------------------------------------------------------------------- /test/conf/slave.conf: -------------------------------------------------------------------------------- 1 | port 6381 2 | bind ::1 127.0.0.1 3 | unixsocket /tmp/redis6381.sock 4 | unixsocketperm 700 5 | slaveof localhost 6379 6 | masterauth porkchopsandwiches 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | benchmarks/ 3 | test/ 4 | .nyc_output/ 5 | coverage/ 6 | .github/ 7 | .eslintignore 8 | .eslintrc 9 | .tern-port 10 | *.log 11 | *.rdb 12 | *.out 13 | *.yml 14 | -------------------------------------------------------------------------------- /examples/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | // The client stashes the password and will reauthenticate on every connect. 5 | redis.createClient({ 6 | password: 'somepass' 7 | }); 8 | -------------------------------------------------------------------------------- /examples/mget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var client = require('redis').createClient(); 4 | 5 | client.mget(['sessions started', 'sessions started', 'foo'], function (err, res) { 6 | console.dir(res); 7 | }); 8 | -------------------------------------------------------------------------------- /test/conf/rename.conf: -------------------------------------------------------------------------------- 1 | port 6379 2 | bind ::1 127.0.0.1 3 | unixsocket /tmp/redis.sock 4 | unixsocketperm 700 5 | rename-command SET 807081f5afa96845a02816a28b7258c3 6 | rename-command GET f397808a43ceca3963e22b4e13deb672 7 | rename-command GETRANGE 9e3102b15cf231c4e9e940f284744fe0 8 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var index = require('../'); 4 | 5 | function debug () { 6 | if (index.debug_mode) { 7 | var data = Array.prototype.slice.call(arguments); 8 | data.unshift(new Date().toISOString()); 9 | console.error.apply(null, data); 10 | } 11 | } 12 | 13 | module.exports = debug; 14 | -------------------------------------------------------------------------------- /test/conf/stunnel.conf.template: -------------------------------------------------------------------------------- 1 | pid = __dirname/stunnel.pid 2 | ; output = __dirname/stunnel.log 3 | CAfile = __dirname/redis.js.org.cert 4 | cert = __dirname/redis.js.org.cert 5 | key = __dirname/redis.js.org.key 6 | client = no 7 | foreground = yes 8 | debug = 7 9 | [redis] 10 | accept = 127.0.0.1:6380 11 | connect = 127.0.0.1:6379 12 | -------------------------------------------------------------------------------- /examples/monitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var client = require('../index').createClient(); 4 | var util = require('util'); 5 | 6 | client.monitor(function (err, res) { 7 | console.log('Entering monitoring mode.'); 8 | }); 9 | 10 | client.on('monitor', function (time, args) { 11 | console.log(time + ': ' + util.inspect(args)); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/eval.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('../index'); 4 | var client = redis.createClient(); 5 | 6 | client.eval('return 100.5', 0, function (err, res) { 7 | console.dir(err); 8 | console.dir(res); 9 | }); 10 | 11 | client.eval([ 'return 100.5', 0 ], function (err, res) { 12 | console.dir(err); 13 | console.dir(res); 14 | }); 15 | -------------------------------------------------------------------------------- /lib/command.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG); 4 | 5 | function Command (command, args, callback, call_on_write) { 6 | this.command = command; 7 | this.args = args; 8 | this.buffer_args = false; 9 | this.callback = callback; 10 | this.call_on_write = call_on_write; 11 | if (betterStackTraces) { 12 | this.error = new Error(); 13 | } 14 | } 15 | 16 | module.exports = Command; 17 | -------------------------------------------------------------------------------- /examples/sort.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | 6 | client.sadd('mylist', 1); 7 | client.sadd('mylist', 2); 8 | client.sadd('mylist', 3); 9 | 10 | client.set('weight_1', 5); 11 | client.set('weight_2', 500); 12 | client.set('weight_3', 1); 13 | 14 | client.set('object_1', 'foo'); 15 | client.set('object_2', 'bar'); 16 | client.set('object_3', 'qux'); 17 | 18 | client.sort('mylist', 'by', 'weight_*', 'get', 'object_*', redis.print); 19 | // Prints Reply: qux,foo,bar 20 | -------------------------------------------------------------------------------- /examples/subqueries.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Sending commands in response to other commands. 4 | // This example runs 'type' against every key in the database 5 | // 6 | var client = require('redis').createClient(); 7 | 8 | client.keys('*', function (err, keys) { 9 | keys.forEach(function (key, pos) { 10 | client.type(key, function (err, keytype) { 11 | console.log(key + ' is ' + keytype); 12 | if (pos === (keys.length - 1)) { 13 | client.quit(); 14 | } 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Pull Request check-list 2 | 3 | _Please make sure to review and check all of these items:_ 4 | 5 | - [ ] Does `npm test` pass with this change (including linting)? 6 | - [ ] Is the new or changed code fully tested? 7 | - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? 8 | 9 | _NOTE: these things are not required to open a PR and can be done 10 | afterwards / while the PR is open._ 11 | 12 | ### Description of change 13 | 14 | _Please provide a description of the change here._ -------------------------------------------------------------------------------- /examples/subquery.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var client = require('redis').createClient(); 4 | 5 | // build a map of all keys and their types 6 | client.keys('*', function (err, all_keys) { 7 | var key_types = {}; 8 | 9 | all_keys.forEach(function (key, pos) { // use second arg of forEach to get pos 10 | client.type(key, function (err, type) { 11 | key_types[key] = type; 12 | if (pos === all_keys.length - 1) { // callbacks all run in order 13 | console.dir(key_types); 14 | } 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | env: 4 | - CXX=g++-4.8 TRAVIS=true 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | node_js: 12 | - "0.10" 13 | - "0.12" 14 | - "4" 15 | - "6" 16 | - "8" 17 | after_success: npm run coveralls 18 | before_script: 19 | # Add an IPv6 config - see the corresponding Travis issue 20 | # https://github.com/travis-ci/travis-ci/issues/8361 21 | - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then 22 | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; 23 | fi 24 | -------------------------------------------------------------------------------- /test/lib/unref.js: -------------------------------------------------------------------------------- 1 | // spawned by the unref tests in node_redis.spec.js. 2 | // when configured, unref causes the client to exit 3 | // as soon as there are no outstanding commands. 4 | 'use strict'; 5 | 6 | var redis = require('../../index'); 7 | var HOST = process.argv[2] || '127.0.0.1'; 8 | var PORT = process.argv[3]; 9 | var args = PORT ? [PORT, HOST] : [HOST]; 10 | 11 | var c = redis.createClient.apply(redis, args); 12 | c.info(function (err, reply) { 13 | if (err) process.exit(-1); 14 | if (!reply.length) process.exit(-1); 15 | process.stdout.write(reply.length.toString()); 16 | }); 17 | c.unref(); 18 | -------------------------------------------------------------------------------- /test/lib/good-traces.js: -------------------------------------------------------------------------------- 1 | // Spawned by the good_stacks.spec.js tests 2 | 'use strict'; 3 | 4 | var assert = require('assert'); 5 | var redis = require('../../index'); 6 | var client = redis.createClient(); 7 | 8 | // Both error cases would normally return bad stack traces 9 | client.set('foo', function (err, res) { 10 | assert(/good-traces.js:9:8/.test(err.stack)); 11 | client.set('foo', 'bar', function (err, res) { 12 | assert(/good-traces.js:11:12/.test(err.stack)); 13 | client.quit(function () { 14 | process.exit(0); 15 | }); 16 | }); 17 | process.nextTick(function () { 18 | client.stream.destroy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/extend.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | 6 | // Extend the RedisClient prototype to add a custom method 7 | // This one converts the results from 'INFO' into a JavaScript Object 8 | 9 | redis.RedisClient.prototype.parse_info = function (callback) { 10 | this.info(function (err, res) { 11 | var lines = res.toString().split('\r\n').sort(); 12 | var obj = {}; 13 | lines.forEach(function (line) { 14 | var parts = line.split(':'); 15 | if (parts[1]) { 16 | obj[parts[0]] = parts[1]; 17 | } 18 | }); 19 | callback(obj); 20 | }); 21 | }; 22 | 23 | client.parse_info(function (info) { 24 | console.dir(info); 25 | client.quit(); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/unix_socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient('/tmp/redis.sock'); 5 | var profiler = require('v8-profiler'); 6 | 7 | client.on('connect', function () { 8 | console.log('Got Unix socket connection.'); 9 | }); 10 | 11 | client.on('error', function (err) { 12 | console.log(err.message); 13 | }); 14 | 15 | client.set('space chars', 'space value'); 16 | 17 | setInterval(function () { 18 | client.get('space chars'); 19 | }, 100); 20 | 21 | function done () { 22 | client.info(function (err, reply) { 23 | console.log(reply.toString()); 24 | client.quit(); 25 | }); 26 | } 27 | 28 | setTimeout(function () { 29 | console.log('Taking snapshot.'); 30 | profiler.takeSnapshot(); 31 | done(); 32 | }, 5000); 33 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | 6 | client.on('error', function (err) { 7 | console.log('error event - ' + client.host + ':' + client.port + ' - ' + err); 8 | }); 9 | 10 | client.set('string key', 'string val', redis.print); 11 | client.hset('hash key', 'hashtest 1', 'some value', redis.print); 12 | client.hset(['hash key', 'hashtest 2', 'some other value'], redis.print); 13 | client.hkeys('hash key', function (err, replies) { 14 | if (err) { 15 | return console.error('error response - ' + err); 16 | } 17 | 18 | console.log(replies.length + ' replies:'); 19 | replies.forEach(function (reply, i) { 20 | console.log(' ' + i + ': ' + reply); 21 | }); 22 | }); 23 | 24 | client.quit(function (err, res) { 25 | console.log('Exiting from quit command.'); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/backpressure_drain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('../index'); 4 | var client = redis.createClient(); 5 | var remaining_ops = 100000; 6 | var paused = false; 7 | 8 | function op () { 9 | if (remaining_ops <= 0) { 10 | console.error('Finished.'); 11 | process.exit(0); 12 | } 13 | 14 | remaining_ops--; 15 | client.hset('test hash', 'val ' + remaining_ops, remaining_ops); 16 | if (client.should_buffer === true) { 17 | console.log('Pausing at ' + remaining_ops); 18 | paused = true; 19 | } else { 20 | setTimeout(op, 1); 21 | } 22 | } 23 | 24 | client.on('drain', function () { 25 | if (paused) { 26 | console.log('Resuming at ' + remaining_ops); 27 | paused = false; 28 | process.nextTick(op); 29 | } else { 30 | console.log('Got drain while not paused at ' + remaining_ops); 31 | } 32 | }); 33 | 34 | op(); 35 | -------------------------------------------------------------------------------- /examples/multi2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | 6 | // start a separate command queue for multi 7 | var multi = client.multi(); 8 | multi.incr('incr thing', redis.print); 9 | multi.incr('incr other thing', redis.print); 10 | 11 | // runs immediately 12 | client.mset('incr thing', 100, 'incr other thing', 1, redis.print); 13 | 14 | // drains multi queue and runs atomically 15 | multi.exec(function (err, replies) { 16 | console.log(replies); // 101, 2 17 | }); 18 | 19 | // you can re-run the same transaction if you like 20 | multi.exec(function (err, replies) { 21 | console.log(replies); // 102, 3 22 | client.quit(); 23 | }); 24 | 25 | client.multi([ 26 | ['mget', 'multifoo', 'multibar', redis.print], 27 | ['incr', 'multifoo'], 28 | ['incr', 'multibar'] 29 | ]).exec(function (err, replies) { 30 | console.log(replies.toString()); 31 | }); 32 | -------------------------------------------------------------------------------- /test/commands/scard.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'scard' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('returns the number of values in a set', function (done) { 22 | client.sadd('foo', [1, 2, 3], helper.isNumber(3)); 23 | client.scard('foo', helper.isNumber(3, done)); 24 | }); 25 | 26 | afterEach(function () { 27 | client.end(true); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Thanks for wanting to report an issue you've found in node_redis. Please delete 2 | this text and fill in the template below. Please note that the issue tracker is only 3 | for bug reports or feature requests. If you have a question, please ask that on [gitter]. 4 | If unsure about something, just do as best as you're able._ 5 | 6 | _Note that it will be much easier to fix the issue if a test case that reproduces 7 | the problem is provided. It is of course not always possible to reduce your code 8 | to a small test case, but it's highly appreciated to have as much data as possible. 9 | Thank you!_ 10 | 11 | * **Version**: What node_redis and what redis version is the issue happening on? 12 | * **Platform**: What platform / version? (For example Node.js 0.10 or Node.js 5.7.0 on Windows 7 / Ubuntu 15.10 / Azure) 13 | * **Description**: Description of your issue, stack traces from errors and code that reproduces the issue 14 | 15 | [gitter]: https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge -------------------------------------------------------------------------------- /test/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // helpers for configuring a redis client in 4 | // its various modes, ipV6, ipV4, socket. 5 | var redis = require('../../index'); 6 | var bluebird = require('bluebird'); 7 | 8 | // Promisify everything 9 | bluebird.promisifyAll(redis.RedisClient.prototype); 10 | bluebird.promisifyAll(redis.Multi.prototype); 11 | 12 | var config = { 13 | redis: redis, 14 | PORT: 6379, 15 | HOST: { 16 | IPv4: '127.0.0.1', 17 | IPv6: '::1' 18 | }, 19 | configureClient: function (parser, ip, opts) { 20 | var args = []; 21 | // Do not manipulate the opts => copy them each time 22 | opts = opts ? JSON.parse(JSON.stringify(opts)) : {}; 23 | 24 | if (ip.match(/\.sock/)) { 25 | args.push(ip); 26 | } else { 27 | args.push(config.PORT); 28 | args.push(config.HOST[ip]); 29 | opts.family = ip; 30 | } 31 | 32 | opts.parser = parser; 33 | args.push(opts); 34 | 35 | return args; 36 | } 37 | }; 38 | 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE - "MIT License" 2 | 3 | Copyright (c) 2016 by NodeRedis 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/psubscribe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client1 = redis.createClient(); 5 | var client2 = redis.createClient(); 6 | var client3 = redis.createClient(); 7 | var client4 = redis.createClient(); 8 | var msg_count = 0; 9 | 10 | client1.on('psubscribe', function (pattern, count) { 11 | console.log('client1 psubscribed to ' + pattern + ', ' + count + ' total subscriptions'); 12 | client2.publish('channeltwo', 'Me!'); 13 | client3.publish('channelthree', 'Me too!'); 14 | client4.publish('channelfour', 'And me too!'); 15 | }); 16 | 17 | client1.on('punsubscribe', function (pattern, count) { 18 | console.log('client1 punsubscribed from ' + pattern + ', ' + count + ' total subscriptions'); 19 | client4.end(); 20 | client3.end(); 21 | client2.end(); 22 | client1.end(); 23 | }); 24 | 25 | client1.on('pmessage', function (pattern, channel, message) { 26 | console.log('(' + pattern + ') client1 received message on ' + channel + ': ' + message); 27 | msg_count += 1; 28 | if (msg_count === 3) { 29 | client1.punsubscribe(); 30 | } 31 | }); 32 | 33 | client1.psubscribe('channel*'); 34 | -------------------------------------------------------------------------------- /test/commands/zscore.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var assert = require('assert'); 6 | var redis = config.redis; 7 | 8 | describe("The 'zscore' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('should return the score of member in the sorted set at key', function (done) { 23 | client.zadd('myzset', 1, 'one'); 24 | client.zscore('myzset', 'one', function (err, res) { 25 | assert.equal(res, 1); 26 | done(); 27 | }); 28 | }); 29 | 30 | afterEach(function () { 31 | client.end(true); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/conf/faulty.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV 3 | BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa 4 | MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP 5 | ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q 6 | XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V 7 | 5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6 8 | +v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd06nW5nQlUA6wVt1JjlLPwBwYsWLsi 9 | YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4 10 | tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/ 11 | 5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr 12 | LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ 13 | v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi 14 | wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo 15 | d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc 16 | 2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm 17 | uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6 18 | w4kLcP8= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/conf/redis.js.org.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDATCCAemgAwIBAgIJALkMmVkQOERnMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV 3 | BAMMDHJlZGlzLmpzLm9yZzAeFw0xNTEwMTkxMjIzMjRaFw0yNTEwMTYxMjIzMjRa 4 | MBcxFTATBgNVBAMMDHJlZGlzLmpzLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEP 5 | ADCCAQoCggEBAJ/DmMTJHf7kyspxI1A/JmOc+KI9vxEcN5qn7IiZuGN7ghE43Q3q 6 | XB2GUkMAuW1POkmM5yi3SuT1UXDR/4Gk7KlbHKMs37AV6PgJXX6oX0zu12LTAT7V 7 | 5byNrYtehSo42l1188dGEMCGaaf0cDntc7A3aW0ZtzrJt+2pu31Uatl2SEJCMra6 8 | +v6O0c9aHMF1cArKeawGqR+jHw6vXFZQbUd05nW5nQlUA6wVt1JjlLPwBwYsWLsi 9 | YQxMC8NqpgAIg5tULSCpKwx5isL/CeotVVGDNZ/G8R1nTrxuygPlc3Qskj57hmV4 10 | tZK4JJxQFi7/9ehvjAvHohKrEPeqV5XL87cCAwEAAaNQME4wHQYDVR0OBBYEFCn/ 11 | 5hB+XY4pVOnaqvrmZMxrLFjLMB8GA1UdIwQYMBaAFCn/5hB+XY4pVOnaqvrmZMxr 12 | LFjLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEduPyTHpXkCVZRQ 13 | v6p+Ug4iVeXpxGCVr34y7EDUMgmuDdqsz1SrmqeDd0VmjZT8htbWw7QBKDPEBsbi 14 | wl606aAn01iM+oUrwbtXxid1xfZj/j6pIhQVkGu7e/8A7Pr4QOP4OMdHB7EmqkAo 15 | d/OLHa9LdKv2UtJHD6U7oVQbdBHrRV62125GMmotpQuSkEfZM6edKNzHPlqV/zJc 16 | 2kGCw3lZC21mTrsSMIC/FQiobPnig4kAvfh0of2rK/XAntlwT8ie1v1aK+jERsfm 17 | uzMihl6XXBdzheq6KdIlf+5STHBIIRcvBoRKr5Va7EhnO03tTzeJowtqDv47yPC6 18 | w4kLcP8= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/commands/sismember.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'sismember' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('returns 0 if the value is not in the set', function (done) { 22 | client.sismember('foo', 'banana', helper.isNumber(0, done)); 23 | }); 24 | 25 | it('returns 1 if the value is in the set', function (done) { 26 | client.sadd('foo', 'banana', helper.isNumber(1)); 27 | client.SISMEMBER('foo', 'banana', helper.isNumber(1, done)); 28 | }); 29 | 30 | afterEach(function () { 31 | client.end(true); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | # Test against these versions of Node.js. 4 | environment: 5 | matrix: 6 | - nodejs_version: "0.10" 7 | - nodejs_version: "0.12" 8 | - nodejs_version: "4" 9 | - nodejs_version: "6" 10 | - nodejs_version: "8" 11 | 12 | pull_requests: 13 | do_not_increment_build_number: true 14 | 15 | platform: Any CPU 16 | shallow_clone: true 17 | 18 | # Install scripts. (runs after repo cloning) 19 | install: 20 | # Install the Redis 21 | - nuget install redis-64 -excludeversion 22 | - redis-64\tools\redis-server.exe --service-install 23 | - redis-64\tools\redis-server.exe --service-start 24 | - '@ECHO Redis Started' 25 | # Get the required Node version 26 | - ps: Install-Product node $env:nodejs_version 27 | # Typical npm stuff 28 | - npm install 29 | 30 | # Post-install test scripts. 31 | test_script: 32 | # Output useful info for debugging. 33 | - node --version 34 | - npm --version 35 | - cmd: npm t 36 | 37 | os: 38 | - Default Azure 39 | - Windows Server 2012 R2 40 | 41 | # Don't actually build using MSBuild 42 | build: off 43 | 44 | # Set build version format here instead of in the admin panel. 45 | version: "{build}" 46 | -------------------------------------------------------------------------------- /test/commands/randomkey.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'randomkey' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns a random key', function (done) { 23 | client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK')); 24 | client.RANDOMKEY([], function (err, results) { 25 | assert.strictEqual(true, /test keys.+/.test(results)); 26 | return done(err); 27 | }); 28 | }); 29 | 30 | afterEach(function () { 31 | client.end(true); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/commands/rpush.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | var assert = require('assert'); 7 | 8 | describe("The 'rpush' command", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('inserts multiple values at a time into a list', function (done) { 23 | client.rpush('test', ['list key', 'should be a list']); 24 | client.lrange('test', 0, -1, function (err, res) { 25 | assert.equal(res[0], 'list key'); 26 | assert.equal(res[1], 'should be a list'); 27 | done(err); 28 | }); 29 | }); 30 | 31 | afterEach(function () { 32 | client.end(true); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/commands/ttl.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'ttl' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns the current ttl on a key', function (done) { 23 | client.set(['ttl key', 'ttl val'], helper.isString('OK')); 24 | client.expire(['ttl key', '100'], helper.isNumber(1)); 25 | client.TTL(['ttl key'], function (err, ttl) { 26 | assert(ttl >= 99); 27 | assert(ttl <= 100); 28 | done(err); 29 | }); 30 | }); 31 | 32 | afterEach(function () { 33 | client.end(true); 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/commands/geoadd.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'geoadd' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('returns 1 if the key exists', function (done) { 22 | helper.serverVersionAtLeast.call(this, client, [3, 2, 0]); 23 | client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR', function (err, res) { 24 | console.log(err, res); 25 | // geoadd is still in the unstable branch. As soon as it reaches the stable one, activate this test 26 | done(); 27 | }); 28 | }); 29 | 30 | afterEach(function () { 31 | client.end(true); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/streams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client1 = redis.createClient(); 5 | var client2 = redis.createClient(); 6 | var client3 = redis.createClient(); 7 | 8 | client1.xadd('mystream', '*', 'field1', 'm1', function (err) { 9 | if(err){ 10 | return console.error(err); 11 | } 12 | client1.xgroup('CREATE', 'mystream', 'mygroup', '$', function (err) { 13 | if(err){ 14 | return console.error(err); 15 | } 16 | }); 17 | 18 | client2.xreadgroup('GROUP', 'mygroup', 'consumer', 'Block', 1000, 'NOACK', 19 | 'STREAMS', 'mystream', '>', function (err, stream) { 20 | if(err){ 21 | return console.error(err); 22 | } 23 | console.log('client2 ' + stream); 24 | }); 25 | 26 | client3.xreadgroup('GROUP', 'mygroup', 'consumer', 'Block', 1000, 'NOACK', 27 | 'STREAMS', 'mystream', '>', function (err, stream) { 28 | if(err){ 29 | return console.error(err); 30 | } 31 | console.log('client3 ' + stream); 32 | }); 33 | 34 | 35 | client1.xadd('mystream', '*', 'field1', 'm2', function (err) { 36 | if(err){ 37 | return console.error(err); 38 | } 39 | }); 40 | 41 | client1.xadd('mystream', '*', 'field1', 'm3', function (err) { 42 | if(err){ 43 | return console.error(err); 44 | } 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/commands/setex.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'setex' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('sets a key with an expiry', function (done) { 23 | client.setex(['setex key', '100', 'setex val'], helper.isString('OK')); 24 | var buffering = client.exists(['setex key'], helper.isNumber(1)); 25 | assert(typeof buffering === 'boolean'); 26 | client.ttl(['setex key'], function (err, ttl) { 27 | assert(ttl > 0); 28 | return done(); 29 | }); 30 | }); 31 | 32 | afterEach(function () { 33 | client.end(true); 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/commands/spop.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'spop' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns a random element from the set', function (done) { 23 | client.sadd('zzz', 'member0', helper.isNumber(1)); 24 | client.scard('zzz', helper.isNumber(1)); 25 | 26 | client.spop('zzz', function (err, value) { 27 | if (err) return done(err); 28 | assert.equal(value, 'member0'); 29 | client.scard('zzz', helper.isNumber(0, done)); 30 | }); 31 | }); 32 | 33 | afterEach(function () { 34 | client.end(true); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/commands/setnx.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'setnx' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('sets key if it does not have a value', function (done) { 22 | client.SETNX('foo', 'banana', helper.isNumber(1)); 23 | client.get('foo', helper.isString('banana', done)); 24 | }); 25 | 26 | it('does not set key if it already has a value', function (done) { 27 | client.set('foo', 'bar', helper.isString('OK')); 28 | client.setnx('foo', 'banana', helper.isNumber(0)); 29 | client.get('foo', helper.isString('bar', done)); 30 | }); 31 | 32 | afterEach(function () { 33 | client.end(true); 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/commands/hincrby.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'hincrby' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | var hash = 'test hash'; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('increments a key that has already been set', function (done) { 23 | var field = 'field 1'; 24 | 25 | client.HSET(hash, field, 33); 26 | client.hincrby(hash, field, 10, helper.isNumber(43, done)); 27 | }); 28 | 29 | it('increments a key that has not been set', function (done) { 30 | var field = 'field 2'; 31 | 32 | client.HINCRBY(hash, field, 10, helper.isNumber(10, done)); 33 | }); 34 | 35 | afterEach(function () { 36 | client.end(true); 37 | }); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/commands/hlen.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'hlen' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('reports the count of keys', function (done) { 22 | var hash = 'test hash'; 23 | var field1 = new Buffer('0123456789'); 24 | var value1 = new Buffer('abcdefghij'); 25 | var field2 = new Buffer(0); 26 | var value2 = new Buffer(0); 27 | 28 | client.HSET(hash, field1, value1, helper.isNumber(1)); 29 | client.HSET(hash, field2, value2, helper.isNumber(1)); 30 | client.HLEN(hash, helper.isNumber(2, done)); 31 | }); 32 | 33 | afterEach(function () { 34 | client.end(true); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/commands/smembers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'smembers' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns all values in a set', function (done) { 23 | client.sadd('foo', 'x', helper.isNumber(1)); 24 | client.sadd('foo', 'y', helper.isNumber(1)); 25 | client.smembers('foo', function (err, values) { 26 | assert.equal(values.length, 2); 27 | var members = values.sort(); 28 | assert.deepEqual(members, [ 'x', 'y' ]); 29 | return done(err); 30 | }); 31 | }); 32 | 33 | afterEach(function () { 34 | client.end(true); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/web_server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // A simple web server that generates dyanmic content based on responses from Redis 4 | 5 | var http = require('http'); 6 | var redis_client = require('redis').createClient(); 7 | 8 | http.createServer(function (request, response) { // The server 9 | response.writeHead(200, { 10 | 'Content-Type': 'text/plain' 11 | }); 12 | 13 | var redis_info, total_requests; 14 | 15 | redis_client.info(function (err, reply) { 16 | redis_info = reply; // stash response in outer scope 17 | }); 18 | redis_client.incr('requests', function (err, reply) { 19 | total_requests = reply; // stash response in outer scope 20 | }); 21 | redis_client.hincrby('ip', request.connection.remoteAddress, 1); 22 | redis_client.hgetall('ip', function (err, reply) { 23 | // This is the last reply, so all of the previous replies must have completed already 24 | response.write('This page was generated after talking to redis.\n\n' + 25 | 'Redis info:\n' + redis_info + '\n' + 26 | 'Total requests: ' + total_requests + '\n\n' + 27 | 'IP count: \n'); 28 | Object.keys(reply).forEach(function (ip) { 29 | response.write(' ' + ip + ': ' + reply[ip] + '\n'); 30 | }); 31 | response.end(); 32 | }); 33 | }).listen(80); 34 | -------------------------------------------------------------------------------- /test/commands/exists.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'exists' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('returns 1 if the key exists', function (done) { 22 | client.set('foo', 'bar'); 23 | client.EXISTS('foo', helper.isNumber(1, done)); 24 | }); 25 | 26 | it('returns 1 if the key exists with array syntax', function (done) { 27 | client.set('foo', 'bar'); 28 | client.EXISTS(['foo'], helper.isNumber(1, done)); 29 | }); 30 | 31 | it('returns 0 if the key does not exist', function (done) { 32 | client.exists('bar', helper.isNumber(0, done)); 33 | }); 34 | 35 | afterEach(function () { 36 | client.end(true); 37 | }); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/commands/rename.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'rename' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('populates the new key', function (done) { 22 | client.set(['foo', 'bar'], helper.isString('OK')); 23 | client.rename(['foo', 'new foo'], helper.isString('OK')); 24 | client.exists(['new foo'], helper.isNumber(1, done)); 25 | }); 26 | 27 | it('removes the old key', function (done) { 28 | client.set(['foo', 'bar'], helper.isString('OK')); 29 | client.RENAME(['foo', 'new foo'], helper.isString('OK')); 30 | client.exists(['foo'], helper.isNumber(0, done)); 31 | }); 32 | 33 | afterEach(function () { 34 | client.end(true); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/commands/msetnx.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'msetnx' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('if any keys exist entire operation fails', function (done) { 22 | client.mset(['mset1', 'val1', 'mset2', 'val2', 'mset3', 'val3'], helper.isString('OK')); 23 | client.MSETNX(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(0)); 24 | client.exists(['mset4'], helper.isNumber(0, done)); 25 | }); 26 | 27 | it('sets multiple keys if all keys are not set', function (done) { 28 | client.msetnx(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(1)); 29 | client.exists(['mset3'], helper.isNumber(1)); 30 | client.exists(['mset3'], helper.isNumber(1, done)); 31 | }); 32 | 33 | afterEach(function () { 34 | client.end(true); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/multi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | var set_size = 20; 6 | 7 | client.sadd('bigset', 'a member'); 8 | client.sadd('bigset', 'another member'); 9 | 10 | while (set_size > 0) { 11 | client.sadd('bigset', 'member ' + set_size); 12 | set_size -= 1; 13 | } 14 | 15 | // multi chain with an individual callback 16 | client.multi() 17 | .scard('bigset') 18 | .smembers('bigset') 19 | .keys('*', function (err, replies) { 20 | client.mget(replies, redis.print); 21 | }) 22 | .dbsize() 23 | .exec(function (err, replies) { 24 | console.log('MULTI got ' + replies.length + ' replies'); 25 | replies.forEach(function (reply, index) { 26 | console.log('Reply ' + index + ': ' + reply.toString()); 27 | }); 28 | }); 29 | 30 | client.mset('incr thing', 100, 'incr other thing', 1, redis.print); 31 | 32 | // start a separate multi command queue 33 | var multi = client.multi(); 34 | multi.incr('incr thing', redis.print); 35 | multi.incr('incr other thing', redis.print); 36 | 37 | // runs immediately 38 | client.get('incr thing', redis.print); // 100 39 | 40 | // drains multi queue and runs atomically 41 | multi.exec(function (err, replies) { 42 | console.log(replies); // 101, 2 43 | }); 44 | 45 | // you can re-run the same transaction if you like 46 | multi.exec(function (err, replies) { 47 | console.log(replies); // 102, 3 48 | client.quit(); 49 | }); 50 | -------------------------------------------------------------------------------- /examples/pub_sub.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client1 = redis.createClient(); 5 | var msg_count = 0; 6 | var client2 = redis.createClient(); 7 | 8 | // Most clients probably don't do much on 'subscribe'. This example uses it to coordinate things within one program. 9 | client1.on('subscribe', function (channel, count) { 10 | console.log('client1 subscribed to ' + channel + ', ' + count + ' total subscriptions'); 11 | if (count === 2) { 12 | client2.publish('a nice channel', 'I am sending a message.'); 13 | client2.publish('another one', 'I am sending a second message.'); 14 | client2.publish('a nice channel', 'I am sending my last message.'); 15 | } 16 | }); 17 | 18 | client1.on('unsubscribe', function (channel, count) { 19 | console.log('client1 unsubscribed from ' + channel + ', ' + count + ' total subscriptions'); 20 | if (count === 0) { 21 | client2.end(); 22 | client1.end(); 23 | } 24 | }); 25 | 26 | client1.on('message', function (channel, message) { 27 | console.log('client1 channel ' + channel + ': ' + message); 28 | msg_count += 1; 29 | if (msg_count === 3) { 30 | client1.unsubscribe(); 31 | } 32 | }); 33 | 34 | client1.on('ready', function () { 35 | // if you need auth, do it here 36 | client1.incr('did a thing'); 37 | client1.subscribe('a nice channel', 'another one'); 38 | }); 39 | 40 | client2.on('ready', function () { 41 | // if you need auth, do it here 42 | }); 43 | -------------------------------------------------------------------------------- /examples/file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Read a file from disk, store it in Redis, then read it back from Redis. 4 | 5 | var redis = require('redis'); 6 | var client = redis.createClient({ 7 | return_buffers: true 8 | }); 9 | var fs = require('fs'); 10 | var assert = require('assert'); 11 | var filename = 'grumpyCat.jpg'; 12 | 13 | // Get the file I use for testing like this: 14 | // curl http://media4.popsugar-assets.com/files/2014/08/08/878/n/1922507/caef16ec354ca23b_thumb_temp_cover_file32304521407524949.xxxlarge/i/Funny-Cat-GIFs.jpg -o grumpyCat.jpg 15 | // or just use your own file. 16 | 17 | // Read a file from fs, store it in Redis, get it back from Redis, write it back to fs. 18 | fs.readFile(filename, function (err, data) { 19 | if (err) throw err; 20 | console.log('Read ' + data.length + ' bytes from filesystem.'); 21 | 22 | client.set(filename, data, redis.print); // set entire file 23 | client.get(filename, function (err, reply) { // get entire file 24 | if (err) { 25 | console.log('Get error: ' + err); 26 | } else { 27 | assert.strictEqual(data.inspect(), reply.inspect()); 28 | fs.writeFile('duplicate_' + filename, reply, function (err) { 29 | if (err) { 30 | console.log('Error on write: ' + err); 31 | } else { 32 | console.log('File written.'); 33 | } 34 | client.end(); 35 | }); 36 | } 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/commands/smove.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'smove' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('moves a value to a set that does not yet exist', function (done) { 22 | client.sadd('foo', 'x', helper.isNumber(1)); 23 | client.smove('foo', 'bar', 'x', helper.isNumber(1)); 24 | client.sismember('foo', 'x', helper.isNumber(0)); 25 | client.sismember('bar', 'x', helper.isNumber(1, done)); 26 | }); 27 | 28 | it('does not move a value if it does not exist in the first set', function (done) { 29 | client.sadd('foo', 'x', helper.isNumber(1)); 30 | client.SMOVE('foo', 'bar', 'y', helper.isNumber(0)); 31 | client.sismember('foo', 'y', helper.isNumber(0)); 32 | client.sismember('bar', 'y', helper.isNumber(0, done)); 33 | }); 34 | 35 | afterEach(function () { 36 | client.end(true); 37 | }); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/commands/expire.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'expire' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('expires key after timeout', function (done) { 22 | client.set(['expiry key', 'bar'], helper.isString('OK')); 23 | client.EXPIRE('expiry key', '1', helper.isNumber(1)); 24 | setTimeout(function () { 25 | client.exists(['expiry key'], helper.isNumber(0, done)); 26 | }, 1050); 27 | }); 28 | 29 | it('expires key after timeout with array syntax', function (done) { 30 | client.set(['expiry key', 'bar'], helper.isString('OK')); 31 | client.EXPIRE(['expiry key', '1'], helper.isNumber(1)); 32 | setTimeout(function () { 33 | client.exists(['expiry key'], helper.isNumber(0, done)); 34 | }, 1050); 35 | }); 36 | 37 | afterEach(function () { 38 | client.end(true); 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/commands/slowlog.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'slowlog' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('logs operations in slowlog', function (done) { 23 | client.config('set', 'slowlog-log-slower-than', 0, helper.isString('OK')); 24 | client.slowlog('reset', helper.isString('OK')); 25 | client.set('foo', 'bar', helper.isString('OK')); 26 | client.get('foo', helper.isString('bar')); 27 | client.SLOWLOG('get', function (err, res) { 28 | assert.equal(res.length, 3); 29 | assert.equal(res[0][3].length, 2); 30 | assert.deepEqual(res[1][3], ['set', 'foo', 'bar']); 31 | assert.deepEqual(res[2][3], ['slowlog', 'reset']); 32 | return done(err); 33 | }); 34 | }); 35 | 36 | afterEach(function () { 37 | client.end(true); 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/commands/renamenx.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'renamenx' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('renames the key if target does not yet exist', function (done) { 22 | client.set('foo', 'bar', helper.isString('OK')); 23 | client.RENAMENX('foo', 'foo2', helper.isNumber(1)); 24 | client.exists('foo', helper.isNumber(0)); 25 | client.exists(['foo2'], helper.isNumber(1, done)); 26 | }); 27 | 28 | it('does not rename the key if the target exists', function (done) { 29 | client.set('foo', 'bar', helper.isString('OK')); 30 | client.set('foo2', 'apple', helper.isString('OK')); 31 | client.renamenx('foo', 'foo2', helper.isNumber(0)); 32 | client.exists('foo', helper.isNumber(1)); 33 | client.exists(['foo2'], helper.isNumber(1, done)); 34 | }); 35 | 36 | afterEach(function () { 37 | client.end(true); 38 | }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/commands/sdiff.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sdiff' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns set difference', function (done) { 23 | client.sadd('foo', 'x', helper.isNumber(1)); 24 | client.sadd('foo', ['a'], helper.isNumber(1)); 25 | client.sadd('foo', 'b', helper.isNumber(1)); 26 | client.sadd(['foo', 'c'], helper.isNumber(1)); 27 | 28 | client.sadd(['bar', 'c', helper.isNumber(1)]); 29 | 30 | client.sadd('baz', 'a', helper.isNumber(1)); 31 | client.sadd('baz', 'd', helper.isNumber(1)); 32 | 33 | client.sdiff('foo', 'bar', 'baz', function (err, values) { 34 | values.sort(); 35 | assert.equal(values.length, 2); 36 | assert.equal(values[0], 'b'); 37 | assert.equal(values[1], 'x'); 38 | return done(err); 39 | }); 40 | }); 41 | 42 | afterEach(function () { 43 | client.end(true); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/commands/sunion.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sunion' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('returns the union of a group of sets', function (done) { 23 | client.sadd('sa', 'a', helper.isNumber(1)); 24 | client.sadd('sa', 'b', helper.isNumber(1)); 25 | client.sadd('sa', 'c', helper.isNumber(1)); 26 | 27 | client.sadd('sb', 'b', helper.isNumber(1)); 28 | client.sadd('sb', 'c', helper.isNumber(1)); 29 | client.sadd('sb', 'd', helper.isNumber(1)); 30 | 31 | client.sadd('sc', 'c', helper.isNumber(1)); 32 | client.sadd('sc', 'd', helper.isNumber(1)); 33 | client.sadd('sc', 'e', helper.isNumber(1)); 34 | 35 | client.sunion('sa', 'sb', 'sc', function (err, union) { 36 | assert.deepEqual(union.sort(), ['a', 'b', 'c', 'd', 'e']); 37 | return done(err); 38 | }); 39 | }); 40 | 41 | afterEach(function () { 42 | client.end(true); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis", 3 | "version": "2.8.0", 4 | "description": "Redis client library", 5 | "keywords": [ 6 | "database", 7 | "redis", 8 | "transaction", 9 | "pipelining", 10 | "performance", 11 | "queue", 12 | "nodejs", 13 | "pubsub", 14 | "backpressure" 15 | ], 16 | "author": "Matt Ranney ", 17 | "license": "MIT", 18 | "main": "./index.js", 19 | "scripts": { 20 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 21 | "coverage": "nyc report --reporter=html", 22 | "benchmark": "node benchmarks/multi_bench.js", 23 | "test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000", 24 | "lint": "eslint . --fix && npm run coverage", 25 | "compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt" 26 | }, 27 | "dependencies": { 28 | "denque": "^1.2.3", 29 | "redis-commands": "^1.4.0", 30 | "redis-parser": "^2.6.0" 31 | }, 32 | "engines": { 33 | "node": ">=0.10.0" 34 | }, 35 | "devDependencies": { 36 | "bluebird": "^3.0.2", 37 | "coveralls": "^2.11.2", 38 | "eslint": "^4.2.0", 39 | "intercept-stdout": "~0.1.2", 40 | "metrics": "^0.1.9", 41 | "mocha": "^3.1.2", 42 | "nyc": "^10.0.0", 43 | "tcp-port-used": "^0.1.2", 44 | "uuid": "^2.0.1", 45 | "win-spawn": "^2.0.0" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "git://github.com/NodeRedis/node_redis.git" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/NodeRedis/node_redis/issues" 53 | }, 54 | "homepage": "https://github.com/NodeRedis/node_redis", 55 | "directories": { 56 | "example": "examples", 57 | "test": "test" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/commands/sdiffstore.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sdiffstore' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('calculates set difference ands stores it in a key', function (done) { 23 | client.sadd('foo', 'x', helper.isNumber(1)); 24 | client.sadd('foo', 'a', helper.isNumber(1)); 25 | client.sadd('foo', 'b', helper.isNumber(1)); 26 | client.sadd('foo', 'c', helper.isNumber(1)); 27 | 28 | client.sadd('bar', 'c', helper.isNumber(1)); 29 | 30 | client.sadd('baz', 'a', helper.isNumber(1)); 31 | client.sadd('baz', 'd', helper.isNumber(1)); 32 | 33 | client.sdiffstore('quux', 'foo', 'bar', 'baz', helper.isNumber(2)); 34 | 35 | client.smembers('quux', function (err, values) { 36 | var members = values.sort(); 37 | assert.deepEqual(members, [ 'b', 'x' ]); 38 | return done(err); 39 | }); 40 | }); 41 | 42 | afterEach(function () { 43 | client.end(true); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/conf/redis.js.org.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAn8OYxMkd/uTKynEjUD8mY5z4oj2/ERw3mqfsiJm4Y3uCETjd 3 | DepcHYZSQwC5bU86SYznKLdK5PVRcNH/gaTsqVscoyzfsBXo+AldfqhfTO7XYtMB 4 | PtXlvI2ti16FKjjaXXXzx0YQwIZpp/RwOe1zsDdpbRm3Osm37am7fVRq2XZIQkIy 5 | trr6/o7Rz1ocwXVwCsp5rAapH6MfDq9cVlBtR3TmdbmdCVQDrBW3UmOUs/AHBixY 6 | uyJhDEwLw2qmAAiDm1QtIKkrDHmKwv8J6i1VUYM1n8bxHWdOvG7KA+VzdCySPnuG 7 | ZXi1krgknFAWLv/16G+MC8eiEqsQ96pXlcvztwIDAQABAoIBAGx4kLCLHCKDlGv+ 8 | hMtnFNltKiJ9acxkLByFBsN4GwjwQk8PHIbmJ8Sj/hYf18WvlRN65zdtuxvYs4K2 9 | EZQkNcqGYdsoDHexaIt/UEs+ZfYF85bVTHMtJt3uE3Ycpq0UDK6H9wvFNnqAyBuQ 10 | iuHJplJuTNYWL6Fqc8aZBwMA3crmwWTelgS+IXLH06E298+KIxbYrWSgrbcmV/Pj 11 | Iwek4CPS0apoJnXxbZDDhAEYGOTxDNXGm+r7BaX/ePM2x1PPib2X9F2XqFV+A4T8 12 | Z91axKJwMrVuTrJkaLPDx9lNUskvvV6KgjZAtYRGpLQTN1AqXJZ09IoK9sNPE4rX 13 | 9fm4awECgYEAzMJkABL0UOoGJhdRf/R0aUOQMO7vYetX5SK9QXcEI04XYFieSaPm 14 | 71st+R/JlJ+LhrTrzGXvyU0tFAQaQZtwaGj/JhbptIpLlGrVf3mqSvxkNi/wzQnn 15 | jBJrrf1ZkDiqtSy7AxGAefWblgK3R1ZU5+0a5jubDkmOltIlbULf0skCgYEAx76l 16 | +5KhWOJPvrjNGB1a8oVXiFzoCpaVVZIhSdl0AtvkKollm5Ou+CKYpE3fKrejRXTD 17 | zmr5bJFXT3VlmIa010cgXJ2btlFa1RiNzgretsOmMcHxLkpAu2/a0L4psHlCrWVK 18 | fxbUW0BYEFVXBDe/4JhFw41YqohdPkFAyo5OUn8CgYBQZGYkzUxVVHzTicY66bym 19 | 85ryS217UY5x7WDHCjZ6shdlgYWsPgjWo0L6k+tuSfHbEr+dwcwSihWPzUiNx7yr 20 | kcXTq51YgA/KluN6KEefJ1clG099AU2C5lyWtGjswgLsHULTopSBzdenXyuce53c 21 | bXBpQq/PPTwZpSqCqoX8WQKBgGe+nsk+jGz1BoRBycyHmrAyD5e04ZR2R9PtFTsd 22 | JYNCoIxzVoHqv8sDdRKJm6q9PKEbl4PDzg7UomuTxxPki1LxD17rQW/9a1cY7LYi 23 | sTBuCAj5+YGYcWypGRaoXlDZeodC/+Fogx1uGw9Is+xt5EwL6tg5tt7D+uIV1Egg 24 | h4+TAoGBAKYA/jn9v93bzPi+w1rlZrlPufRSr4k3mcHae165N/1PnjSguTFIF5DW 25 | +1f5S+XioNyTcfx5gKI8f6wRn1j5zbB24GXgu8dXCzRHC2gzrwq2D9v1od4zP/o7 26 | xFxyiNGOMUJ7uW9d/nEL5Eg4CQKZEkZNmzHhuKNr8wDSr16DhXVK 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/commands/sinterstore.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sinterstore' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('calculates set intersection and stores it in a key', function (done) { 23 | client.sadd('sa', 'a', helper.isNumber(1)); 24 | client.sadd('sa', 'b', helper.isNumber(1)); 25 | client.sadd('sa', 'c', helper.isNumber(1)); 26 | 27 | client.sadd('sb', 'b', helper.isNumber(1)); 28 | client.sadd('sb', 'c', helper.isNumber(1)); 29 | client.sadd('sb', 'd', helper.isNumber(1)); 30 | 31 | client.sadd('sc', 'c', helper.isNumber(1)); 32 | client.sadd('sc', 'd', helper.isNumber(1)); 33 | client.sadd('sc', 'e', helper.isNumber(1)); 34 | 35 | client.sinterstore('foo', 'sa', 'sb', 'sc', helper.isNumber(1)); 36 | 37 | client.smembers('foo', function (err, members) { 38 | assert.deepEqual(members, [ 'c' ]); 39 | return done(err); 40 | }); 41 | }); 42 | 43 | afterEach(function () { 44 | client.end(true); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/commands/zscan.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var assert = require('assert'); 6 | var redis = config.redis; 7 | 8 | describe("The 'zscan' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('return values', function (done) { 23 | if (helper.redisProcess().spawnFailed()) this.skip(); 24 | helper.serverVersionAtLeast.call(this, client, [2, 8, 0]); 25 | var hash = {}; 26 | var set = []; 27 | var zset = ['zset:1']; 28 | for (var i = 0; i < 500; i++) { 29 | hash['key_' + i] = 'value_' + i; 30 | set.push('member_' + i); 31 | zset.push(i, 'z_member_' + i); 32 | } 33 | client.hmset('hash:1', hash); 34 | client.sadd('set:1', set); 35 | client.zadd(zset); 36 | client.zscan('zset:1', 0, 'MATCH', '*', 'COUNT', 500, function (err, res) { 37 | assert(!err); 38 | assert.strictEqual(res.length, 2); 39 | assert.strictEqual(res[1].length, 1000); 40 | done(); 41 | }); 42 | }); 43 | 44 | afterEach(function () { 45 | client.end(true); 46 | }); 47 | }); 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /test/commands/sunionstore.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sunionstore' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('stores the result of a union', function (done) { 23 | client.sadd('sa', 'a', helper.isNumber(1)); 24 | client.sadd('sa', 'b', helper.isNumber(1)); 25 | client.sadd('sa', 'c', helper.isNumber(1)); 26 | 27 | client.sadd('sb', 'b', helper.isNumber(1)); 28 | client.sadd('sb', 'c', helper.isNumber(1)); 29 | client.sadd('sb', 'd', helper.isNumber(1)); 30 | 31 | client.sadd('sc', 'c', helper.isNumber(1)); 32 | client.sadd('sc', 'd', helper.isNumber(1)); 33 | client.sadd('sc', 'e', helper.isNumber(1)); 34 | 35 | client.sunionstore('foo', 'sa', 'sb', 'sc', helper.isNumber(5)); 36 | 37 | client.smembers('foo', function (err, members) { 38 | assert.equal(members.length, 5); 39 | assert.deepEqual(members.sort(), ['a', 'b', 'c', 'd', 'e']); 40 | return done(err); 41 | }); 42 | }); 43 | 44 | afterEach(function () { 45 | client.end(true); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/good_traces.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('./lib/config'); 5 | var fork = require('child_process').fork; 6 | var redis = config.redis; 7 | 8 | describe('stack traces', function () { 9 | 10 | it('should return good traces with NODE_ENV=development set', function (done) { 11 | var external = fork('./test/lib/good-traces.js', { 12 | env: { 13 | NODE_ENV: 'development' 14 | } 15 | }); 16 | 17 | var id = setTimeout(function () { 18 | external.kill(); 19 | done(new Error('Timeout')); 20 | }, 6000); 21 | 22 | external.on('close', function (code) { 23 | clearTimeout(id); 24 | assert.strictEqual(code, 0); 25 | done(); 26 | }); 27 | }); 28 | 29 | it('should return good traces with NODE_DEBUG=redis env set', function (done) { 30 | var external = fork('./test/lib/good-traces.js', { 31 | env: { 32 | NODE_DEBUG: 'redis' 33 | }, 34 | silent: true 35 | }); 36 | 37 | var id = setTimeout(function () { 38 | external.kill(); 39 | done(new Error('Timeout')); 40 | }, 6000); 41 | 42 | external.on('close', function (code) { 43 | clearTimeout(id); 44 | assert.strictEqual(code, 0); 45 | done(); 46 | }); 47 | }); 48 | 49 | // This is always going to return good stack traces 50 | it('should always return good stack traces for rejected offline commands', function (done) { 51 | var client = redis.createClient({ 52 | enable_offline_queue: false 53 | }); 54 | client.set('foo', function (err, res) { 55 | assert(/good_traces.spec.js/.test(err.stack)); 56 | client.quit(done); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /lib/customErrors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var assert = require('assert'); 5 | var RedisError = require('redis-parser').RedisError; 6 | var ADD_STACKTRACE = false; 7 | 8 | function AbortError (obj, stack) { 9 | assert(obj, 'The options argument is required'); 10 | assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object'); 11 | 12 | RedisError.call(this, obj.message, ADD_STACKTRACE); 13 | Object.defineProperty(this, 'message', { 14 | value: obj.message || '', 15 | configurable: true, 16 | writable: true 17 | }); 18 | if (stack || stack === undefined) { 19 | Error.captureStackTrace(this, AbortError); 20 | } 21 | for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) { 22 | this[key] = obj[key]; 23 | } 24 | } 25 | 26 | function AggregateError (obj) { 27 | assert(obj, 'The options argument is required'); 28 | assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object'); 29 | 30 | AbortError.call(this, obj, ADD_STACKTRACE); 31 | Object.defineProperty(this, 'message', { 32 | value: obj.message || '', 33 | configurable: true, 34 | writable: true 35 | }); 36 | Error.captureStackTrace(this, AggregateError); 37 | for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) { 38 | this[key] = obj[key]; 39 | } 40 | } 41 | 42 | util.inherits(AbortError, RedisError); 43 | util.inherits(AggregateError, AbortError); 44 | 45 | Object.defineProperty(AbortError.prototype, 'name', { 46 | value: 'AbortError', 47 | configurable: true, 48 | writable: true 49 | }); 50 | Object.defineProperty(AggregateError.prototype, 'name', { 51 | value: 'AggregateError', 52 | configurable: true, 53 | writable: true 54 | }); 55 | 56 | module.exports = { 57 | AbortError: AbortError, 58 | AggregateError: AggregateError 59 | }; 60 | -------------------------------------------------------------------------------- /test/commands/watch.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'watch' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | var watched = 'foobar'; 13 | 14 | describe('using ' + parser + ' and ' + ip, function () { 15 | var client; 16 | 17 | beforeEach(function (done) { 18 | client = redis.createClient.apply(null, args); 19 | client.once('ready', function () { 20 | client.flushdb(done); 21 | }); 22 | }); 23 | 24 | afterEach(function () { 25 | client.end(true); 26 | }); 27 | 28 | it('does not execute transaction if watched key was modified prior to execution', function (done) { 29 | client.WATCH(watched); 30 | client.incr(watched); 31 | var multi = client.multi(); 32 | multi.incr(watched); 33 | multi.exec(helper.isNull(done)); 34 | }); 35 | 36 | it('successfully modifies other keys independently of transaction', function (done) { 37 | client.set('unwatched', 200); 38 | 39 | client.set(watched, 0); 40 | client.watch(watched); 41 | client.incr(watched); 42 | 43 | client.multi().incr(watched).exec(function (err, replies) { 44 | assert.strictEqual(replies, null, 'Aborted transaction multi-bulk reply should be null.'); 45 | 46 | client.get('unwatched', function (err, reply) { 47 | assert.equal(reply, 200, 'Expected 200, got ' + reply); 48 | return done(err); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/commands/zadd.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var assert = require('assert'); 6 | var redis = config.redis; 7 | 8 | describe("The 'zadd' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('reports an error', function (done) { 23 | if (helper.redisProcess().spawnFailed()) this.skip(); 24 | client.zadd('infinity', [+'5t', 'should not be possible'], helper.isError(done)); 25 | }); 26 | 27 | it('return inf / -inf', function (done) { 28 | if (helper.redisProcess().spawnFailed()) this.skip(); 29 | helper.serverVersionAtLeast.call(this, client, [3, 0, 2]); 30 | client.zadd('infinity', [+Infinity, 'should be inf'], helper.isNumber(1)); 31 | client.zadd('infinity', ['inf', 'should be also be inf'], helper.isNumber(1)); 32 | client.zadd('infinity', -Infinity, 'should be negative inf', helper.isNumber(1)); 33 | client.zadd('infinity', [99999999999999999999999, 'should not be inf'], helper.isNumber(1)); 34 | client.zrange('infinity', 0, -1, 'WITHSCORES', function (err, res) { 35 | assert.equal(res[5], 'inf'); 36 | assert.equal(res[1], '-inf'); 37 | assert.equal(res[3], '9.9999999999999992e+22'); 38 | done(); 39 | }); 40 | }); 41 | 42 | afterEach(function () { 43 | client.end(true); 44 | }); 45 | }); 46 | }); 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /examples/scan.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var redis = require('redis'); 4 | var client = redis.createClient(); 5 | 6 | var cursor = '0'; 7 | 8 | function scan () { 9 | client.scan( 10 | cursor, 11 | 'MATCH', 'q:job:*', 12 | 'COUNT', '10', 13 | function (err, res) { 14 | if (err) throw err; 15 | 16 | // Update the cursor position for the next scan 17 | cursor = res[0]; 18 | // get the SCAN result for this iteration 19 | var keys = res[1]; 20 | 21 | // Remember: more or less than COUNT or no keys may be returned 22 | // See http://redis.io/commands/scan#the-count-option 23 | // Also, SCAN may return the same key multiple times 24 | // See http://redis.io/commands/scan#scan-guarantees 25 | // Additionally, you should always have the code that uses the keys 26 | // before the code checking the cursor. 27 | if (keys.length > 0) { 28 | console.log('Array of matching keys', keys); 29 | } 30 | 31 | // It's important to note that the cursor and returned keys 32 | // vary independently. The scan is never complete until redis 33 | // returns a non-zero cursor. However, with MATCH and large 34 | // collections, most iterations will return an empty keys array. 35 | 36 | // Still, a cursor of zero DOES NOT mean that there are no keys. 37 | // A zero cursor just means that the SCAN is complete, but there 38 | // might be one last batch of results to process. 39 | 40 | // From : 41 | // 'An iteration starts when the cursor is set to 0, 42 | // and terminates when the cursor returned by the server is 0.' 43 | if (cursor === '0') { 44 | return console.log('Iteration complete'); 45 | } 46 | 47 | return scan(); 48 | } 49 | ); 50 | } 51 | scan(); 52 | -------------------------------------------------------------------------------- /test/commands/script.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var crypto = require('crypto'); 6 | var helper = require('../helper'); 7 | var redis = config.redis; 8 | 9 | describe("The 'script' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | var command = 'return 99'; 13 | var commandSha = crypto.createHash('sha1').update(command).digest('hex'); 14 | 15 | describe('using ' + parser + ' and ' + ip, function () { 16 | var client; 17 | 18 | beforeEach(function (done) { 19 | client = redis.createClient.apply(null, args); 20 | client.once('ready', function () { 21 | client.flushdb(done); 22 | }); 23 | }); 24 | 25 | afterEach(function () { 26 | client.end(true); 27 | }); 28 | 29 | it("loads script with client.script('load')", function (done) { 30 | client.script('load', command, function (err, result) { 31 | assert.strictEqual(result, commandSha); 32 | return done(); 33 | }); 34 | }); 35 | 36 | it('allows a loaded script to be evaluated', function (done) { 37 | client.evalsha(commandSha, 0, helper.isNumber(99, done)); 38 | }); 39 | 40 | it('allows a script to be loaded as part of a chained transaction', function (done) { 41 | client.multi().script('load', command).exec(function (err, result) { 42 | assert.strictEqual(result[0], commandSha); 43 | return done(); 44 | }); 45 | }); 46 | 47 | it("allows a script to be loaded using a transaction's array syntax", function (done) { 48 | client.multi([['script', 'load', command]]).exec(function (err, result) { 49 | assert.strictEqual(result[0], commandSha); 50 | return done(); 51 | }); 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/commands/type.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'type' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('reports string type', function (done) { 22 | client.set(['string key', 'should be a string'], helper.isString('OK')); 23 | client.TYPE(['string key'], helper.isString('string', done)); 24 | }); 25 | 26 | it('reports list type', function (done) { 27 | client.rpush(['list key', 'should be a list'], helper.isNumber(1)); 28 | client.type(['list key'], helper.isString('list', done)); 29 | }); 30 | 31 | it('reports set type', function (done) { 32 | client.sadd(['set key', 'should be a set'], helper.isNumber(1)); 33 | client.TYPE(['set key'], helper.isString('set', done)); 34 | }); 35 | 36 | it('reports zset type', function (done) { 37 | client.zadd('zset key', ['10.0', 'should be a zset'], helper.isNumber(1)); 38 | client.TYPE(['zset key'], helper.isString('zset', done)); 39 | }); 40 | 41 | it('reports hash type', function (done) { 42 | client.hset('hash key', 'hashtest', 'should be a hash', helper.isNumber(1)); 43 | client.TYPE(['hash key'], helper.isString('hash', done)); 44 | }); 45 | 46 | it('reports none for null key', function (done) { 47 | client.TYPE('not here yet', helper.isString('none', done)); 48 | }); 49 | 50 | afterEach(function () { 51 | client.end(true); 52 | }); 53 | }); 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /test/commands/del.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../lib/config'); 4 | var helper = require('../helper'); 5 | var redis = config.redis; 6 | 7 | describe("The 'del' method", function () { 8 | 9 | helper.allTests(function (parser, ip, args) { 10 | 11 | describe('using ' + parser + ' and ' + ip, function () { 12 | var client; 13 | 14 | beforeEach(function (done) { 15 | client = redis.createClient.apply(null, args); 16 | client.once('ready', function () { 17 | client.flushdb(done); 18 | }); 19 | }); 20 | 21 | it('allows a single key to be deleted', function (done) { 22 | client.set('foo', 'bar'); 23 | client.DEL('foo', helper.isNumber(1)); 24 | client.get('foo', helper.isNull(done)); 25 | }); 26 | 27 | it('allows del to be called on a key that does not exist', function (done) { 28 | client.del('foo', helper.isNumber(0, done)); 29 | }); 30 | 31 | it('allows multiple keys to be deleted', function (done) { 32 | client.mset('foo', 'bar', 'apple', 'banana'); 33 | client.del('foo', 'apple', helper.isNumber(2)); 34 | client.get('foo', helper.isNull()); 35 | client.get('apple', helper.isNull(done)); 36 | }); 37 | 38 | it('allows multiple keys to be deleted with the array syntax', function (done) { 39 | client.mset('foo', 'bar', 'apple', 'banana'); 40 | client.del(['foo', 'apple'], helper.isNumber(2)); 41 | client.get('foo', helper.isNull()); 42 | client.get('apple', helper.isNull(done)); 43 | }); 44 | 45 | it('allows multiple keys to be deleted with the array syntax and no callback', function (done) { 46 | client.mset('foo', 'bar', 'apple', 'banana'); 47 | client.del(['foo', 'apple']); 48 | client.get('foo', helper.isNull()); 49 | client.get('apple', helper.isNull(done)); 50 | }); 51 | 52 | afterEach(function () { 53 | client.end(true); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/commands/sinter.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sinter' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('handles two sets being intersected', function (done) { 23 | client.sadd('sa', 'a', helper.isNumber(1)); 24 | client.sadd('sa', 'b', helper.isNumber(1)); 25 | client.sadd('sa', 'c', helper.isNumber(1)); 26 | 27 | client.sadd('sb', 'b', helper.isNumber(1)); 28 | client.sadd('sb', 'c', helper.isNumber(1)); 29 | client.sadd('sb', 'd', helper.isNumber(1)); 30 | 31 | client.SINTER('sa', 'sb', function (err, intersection) { 32 | assert.equal(intersection.length, 2); 33 | assert.deepEqual(intersection.sort(), [ 'b', 'c' ]); 34 | return done(err); 35 | }); 36 | }); 37 | 38 | it('handles three sets being intersected', function (done) { 39 | client.sadd('sa', 'a', helper.isNumber(1)); 40 | client.sadd('sa', 'b', helper.isNumber(1)); 41 | client.sadd('sa', 'c', helper.isNumber(1)); 42 | 43 | client.sadd('sb', 'b', helper.isNumber(1)); 44 | client.sadd('sb', 'c', helper.isNumber(1)); 45 | client.sadd('sb', 'd', helper.isNumber(1)); 46 | 47 | client.sadd('sc', 'c', helper.isNumber(1)); 48 | client.sadd('sc', 'd', helper.isNumber(1)); 49 | client.sadd('sc', 'e', helper.isNumber(1)); 50 | 51 | client.sinter('sa', 'sb', 'sc', function (err, intersection) { 52 | assert.equal(intersection.length, 1); 53 | assert.equal(intersection[0], 'c'); 54 | return done(err); 55 | }); 56 | }); 57 | 58 | afterEach(function () { 59 | client.end(true); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/commands/sadd.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'sadd' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('allows a single value to be added to the set', function (done) { 23 | client.SADD('set0', 'member0', helper.isNumber(1)); 24 | client.smembers('set0', function (err, res) { 25 | assert.ok(~res.indexOf('member0')); 26 | return done(err); 27 | }); 28 | }); 29 | 30 | it('does not add the same value to the set twice', function (done) { 31 | client.sadd('set0', 'member0', helper.isNumber(1)); 32 | client.SADD('set0', 'member0', helper.isNumber(0, done)); 33 | }); 34 | 35 | it('allows multiple values to be added to the set', function (done) { 36 | client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3)); 37 | client.smembers('set0', function (err, res) { 38 | assert.strictEqual(res.length, 3); 39 | assert.ok(~res.indexOf('member0')); 40 | assert.ok(~res.indexOf('member1')); 41 | assert.ok(~res.indexOf('member2')); 42 | return done(err); 43 | }); 44 | }); 45 | 46 | it('allows multiple values to be added to the set with a different syntax', function (done) { 47 | client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)); 48 | client.smembers('set0', function (err, res) { 49 | assert.strictEqual(res.length, 3); 50 | assert.ok(~res.indexOf('member0')); 51 | assert.ok(~res.indexOf('member1')); 52 | assert.ok(~res.indexOf('member2')); 53 | return done(err); 54 | }); 55 | }); 56 | 57 | afterEach(function () { 58 | client.end(true); 59 | }); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/commands/keys.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var crypto = require('crypto'); 6 | var helper = require('../helper'); 7 | var redis = config.redis; 8 | 9 | describe("The 'keys' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var client; 15 | 16 | beforeEach(function (done) { 17 | client = redis.createClient.apply(null, args); 18 | client.once('ready', function () { 19 | client.flushall(done); 20 | }); 21 | }); 22 | 23 | it('returns matching keys', function (done) { 24 | client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK')); 25 | client.KEYS('test keys*', function (err, results) { 26 | assert.strictEqual(2, results.length); 27 | assert.ok(~results.indexOf('test keys 1')); 28 | assert.ok(~results.indexOf('test keys 2')); 29 | return done(err); 30 | }); 31 | }); 32 | 33 | it('handles a large packet size', function (done) { 34 | var keys_values = []; 35 | 36 | for (var i = 0; i < 200; i++) { 37 | var key_value = [ 38 | 'multibulk:' + crypto.randomBytes(256).toString('hex'), // use long strings as keys to ensure generation of large packet 39 | 'test val ' + i 40 | ]; 41 | keys_values.push(key_value); 42 | } 43 | 44 | client.mset(keys_values.reduce(function (a, b) { 45 | return a.concat(b); 46 | }), helper.isString('OK')); 47 | 48 | client.keys('multibulk:*', function (err, results) { 49 | assert.deepEqual(keys_values.map(function (val) { 50 | return val[0]; 51 | }).sort(), results.sort()); 52 | return done(err); 53 | }); 54 | }); 55 | 56 | it('handles an empty response', function (done) { 57 | client.KEYS(['users:*'], function (err, results) { 58 | assert.strictEqual(results.length, 0); 59 | assert.ok(Array.isArray(results)); 60 | return done(err); 61 | }); 62 | }); 63 | 64 | afterEach(function () { 65 | client.end(true); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: false 4 | 5 | rules: 6 | # Possible Errors 7 | # http://eslint.org/docs/rules/#possible-errors 8 | comma-dangle: [2, "only-multiline"] 9 | no-constant-condition: 2 10 | no-control-regex: 2 11 | no-debugger: 2 12 | no-dupe-args: 2 13 | no-dupe-keys: 2 14 | no-duplicate-case: 2 15 | no-empty: 2 16 | no-empty-character-class: 2 17 | no-ex-assign: 2 18 | no-extra-boolean-cast : 2 19 | no-extra-parens: [2, "functions"] 20 | no-extra-semi: 2 21 | no-func-assign: 2 22 | no-invalid-regexp: 2 23 | no-irregular-whitespace: 2 24 | no-negated-in-lhs: 2 25 | no-obj-calls: 2 26 | no-regex-spaces: 2 27 | no-sparse-arrays: 2 28 | no-inner-declarations: 2 29 | no-unexpected-multiline: 2 30 | no-unreachable: 2 31 | use-isnan: 2 32 | valid-typeof: 2 33 | 34 | # Best Practices 35 | # http://eslint.org/docs/rules/#best-practices 36 | array-callback-return: 2 37 | block-scoped-var: 2 38 | dot-notation: 2 39 | eqeqeq: 2 40 | no-else-return: 2 41 | no-extend-native: 2 42 | no-floating-decimal: 2 43 | no-extra-bind: 2 44 | no-fallthrough: 2 45 | no-labels: 2 46 | no-lone-blocks: 2 47 | no-loop-func: 2 48 | no-multi-spaces: 2 49 | no-multi-str: 2 50 | no-native-reassign: 2 51 | no-new-wrappers: 2 52 | no-octal: 2 53 | no-proto: 2 54 | no-redeclare: 2 55 | no-return-assign: 2 56 | no-self-assign: 2 57 | no-self-compare: 2 58 | no-sequences: 2 59 | no-throw-literal: 2 60 | no-useless-call: 2 61 | no-useless-concat: 2 62 | no-useless-escape: 2 63 | no-void: 2 64 | no-unmodified-loop-condition: 2 65 | yoda: 2 66 | 67 | # Strict Mode 68 | # http://eslint.org/docs/rules/#strict-mode 69 | strict: [2, "global"] 70 | 71 | # Variables 72 | # http://eslint.org/docs/rules/#variables 73 | no-delete-var: 2 74 | no-shadow-restricted-names: 2 75 | no-undef: 2 76 | no-unused-vars: [2, {"args": "none"}] 77 | 78 | # http://eslint.org/docs/rules/#nodejs-and-commonjs 79 | no-mixed-requires: 2 80 | no-new-require: 2 81 | no-path-concat: 2 82 | 83 | # Stylistic Issues 84 | # http://eslint.org/docs/rules/#stylistic-issues 85 | comma-spacing: 2 86 | eol-last: 2 87 | indent: [2, 4, {SwitchCase: 2}] 88 | keyword-spacing: 2 89 | max-len: [2, 200, 2] 90 | new-parens: 2 91 | no-mixed-spaces-and-tabs: 2 92 | no-multiple-empty-lines: [2, {max: 2}] 93 | no-trailing-spaces: 2 94 | quotes: [2, "single", "avoid-escape"] 95 | semi: 2 96 | space-before-blocks: [2, "always"] 97 | space-before-function-paren: [2, "always"] 98 | space-in-parens: [2, "never"] 99 | space-infix-ops: 2 100 | space-unary-ops: 2 101 | 102 | globals: 103 | it: true 104 | describe: true 105 | before: true 106 | after: true 107 | beforeEach: true 108 | afterEach: true 109 | -------------------------------------------------------------------------------- /test/lib/stunnel-process.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // helper to start and stop the stunnel process. 4 | var spawn = require('child_process').spawn; 5 | var EventEmitter = require('events'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var util = require('util'); 9 | 10 | // Newer Node.js versions > 0.10 return the EventEmitter right away and using .EventEmitter was deprecated 11 | if (typeof EventEmitter !== 'function') { 12 | EventEmitter = EventEmitter.EventEmitter; 13 | } 14 | 15 | function once (cb) { 16 | var called = false; 17 | return function () { 18 | if (called) return; 19 | called = true; 20 | cb.apply(this, arguments); 21 | }; 22 | } 23 | 24 | function StunnelProcess (conf_dir) { 25 | EventEmitter.call(this); 26 | 27 | // set up an stunnel to redis; edit the conf file to include required absolute paths 28 | var conf_file = path.resolve(conf_dir, 'stunnel.conf'); 29 | var conf_text = fs.readFileSync(conf_file + '.template').toString().replace(/__dirname/g, conf_dir); 30 | 31 | fs.writeFileSync(conf_file, conf_text); 32 | var stunnel = this.stunnel = spawn('stunnel', [conf_file]); 33 | 34 | // handle child process events, and failure to set up tunnel 35 | var self = this; 36 | this.timer = setTimeout(function () { 37 | self.emit('error', new Error('Timeout waiting for stunnel to start')); 38 | }, 8000); 39 | 40 | stunnel.on('error', function (err) { 41 | self.clear(); 42 | self.emit('error', err); 43 | }); 44 | 45 | stunnel.on('exit', function (code) { 46 | self.clear(); 47 | if (code === 0) { 48 | self.emit('stopped'); 49 | } else { 50 | self.emit('error', new Error('Stunnel exited unexpectedly; code = ' + code)); 51 | } 52 | }); 53 | 54 | // wait to stunnel to start 55 | stunnel.stderr.on('data', function (data) { 56 | if (data.toString().match(/Service.+redis.+bound/)) { 57 | clearTimeout(this.timer); 58 | self.emit('started'); 59 | } 60 | }); 61 | } 62 | util.inherits(StunnelProcess, EventEmitter); 63 | 64 | StunnelProcess.prototype.clear = function () { 65 | this.stunnel = null; 66 | clearTimeout(this.timer); 67 | }; 68 | 69 | StunnelProcess.prototype.stop = function (done) { 70 | if (this.stunnel) { 71 | this.stunnel.kill(); 72 | } 73 | }; 74 | 75 | module.exports = { 76 | start: function (done, conf_dir) { 77 | done = once(done); 78 | var stunnel = new StunnelProcess(conf_dir); 79 | stunnel.once('error', done.bind(done)); 80 | stunnel.once('started', done.bind(done, null, stunnel)); 81 | }, 82 | stop: function (stunnel, done) { 83 | stunnel.removeAllListeners(); 84 | stunnel.stop(); 85 | stunnel.once('error', done.bind(done)); 86 | stunnel.once('stopped', done.bind(done, null)); 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /test/commands/srem.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'srem' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushdb(done); 19 | }); 20 | }); 21 | 22 | it('removes a value', function (done) { 23 | client.sadd('set0', 'member0', helper.isNumber(1)); 24 | client.srem('set0', 'member0', helper.isNumber(1)); 25 | client.scard('set0', helper.isNumber(0, done)); 26 | }); 27 | 28 | it('handles attempting to remove a missing value', function (done) { 29 | client.SREM('set0', 'member0', helper.isNumber(0, done)); 30 | }); 31 | 32 | it('allows multiple values to be removed', function (done) { 33 | client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3)); 34 | client.SREM('set0', ['member1', 'member2'], helper.isNumber(2)); 35 | client.smembers('set0', function (err, res) { 36 | assert.strictEqual(res.length, 1); 37 | assert.ok(~res.indexOf('member0')); 38 | return done(err); 39 | }); 40 | }); 41 | 42 | it('allows multiple values to be removed with send_command', function (done) { 43 | client.send_command('sadd', ['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)); 44 | client.send_command('srem', ['set0', 'member1', 'member2'], helper.isNumber(2)); 45 | client.smembers('set0', function (err, res) { 46 | assert.strictEqual(res.length, 1); 47 | assert.ok(~res.indexOf('member0')); 48 | return done(err); 49 | }); 50 | }); 51 | 52 | it('handles a value missing from the set of values being removed', function (done) { 53 | client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)); 54 | client.SREM(['set0', 'member3', 'member4'], helper.isNumber(0)); 55 | client.smembers('set0', function (err, res) { 56 | assert.strictEqual(res.length, 3); 57 | assert.ok(~res.indexOf('member0')); 58 | assert.ok(~res.indexOf('member1')); 59 | assert.ok(~res.indexOf('member2')); 60 | return done(err); 61 | }); 62 | }); 63 | 64 | afterEach(function () { 65 | client.end(true); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/commands/hmget.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'hmget' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | var hash = 'test hash'; 15 | 16 | beforeEach(function (done) { 17 | client = redis.createClient.apply(null, args); 18 | client.once('error', done); 19 | client.once('ready', function () { 20 | client.flushdb(); 21 | client.HMSET(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'}, helper.isString('OK', done)); 22 | }); 23 | }); 24 | 25 | it('allows keys to be specified using multiple arguments', function (done) { 26 | client.hmget(hash, '0123456789', 'some manner of key', function (err, reply) { 27 | assert.strictEqual('abcdefghij', reply[0].toString()); 28 | assert.strictEqual('a type of value', reply[1].toString()); 29 | return done(err); 30 | }); 31 | }); 32 | 33 | it('allows keys to be specified by passing an array without manipulating the array', function (done) { 34 | var data = ['0123456789', 'some manner of key']; 35 | client.HMGET(hash, data, function (err, reply) { 36 | assert.strictEqual(data.length, 2); 37 | assert.strictEqual('abcdefghij', reply[0].toString()); 38 | assert.strictEqual('a type of value', reply[1].toString()); 39 | return done(err); 40 | }); 41 | }); 42 | 43 | it('allows keys to be specified by passing an array as first argument', function (done) { 44 | client.HMGET([hash, '0123456789', 'some manner of key'], function (err, reply) { 45 | assert.strictEqual('abcdefghij', reply[0].toString()); 46 | assert.strictEqual('a type of value', reply[1].toString()); 47 | return done(err); 48 | }); 49 | }); 50 | 51 | it('allows a single key to be specified in an array', function (done) { 52 | client.HMGET(hash, ['0123456789'], function (err, reply) { 53 | assert.strictEqual('abcdefghij', reply[0].toString()); 54 | return done(err); 55 | }); 56 | }); 57 | 58 | it('allows keys to be specified that have not yet been set', function (done) { 59 | client.HMGET(hash, 'missing thing', 'another missing thing', function (err, reply) { 60 | assert.strictEqual(null, reply[0]); 61 | assert.strictEqual(null, reply[1]); 62 | return done(err); 63 | }); 64 | }); 65 | 66 | afterEach(function () { 67 | client.end(true); 68 | }); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/commands/info.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'info' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('ready', function () { 18 | client.flushall(done); 19 | }); 20 | }); 21 | 22 | afterEach(function () { 23 | client.end(true); 24 | }); 25 | 26 | it('update serverInfo after a info command', function (done) { 27 | client.set('foo', 'bar'); 28 | client.info(); 29 | client.select(2, function () { 30 | assert.strictEqual(client.serverInfo.db2, undefined); 31 | }); 32 | client.set('foo', 'bar'); 33 | client.info(); 34 | setTimeout(function () { 35 | assert.strictEqual(typeof client.serverInfo.db2, 'object'); 36 | done(); 37 | }, 30); 38 | }); 39 | 40 | it('works with optional section provided with and without callback', function (done) { 41 | client.set('foo', 'bar'); 42 | client.info('keyspace'); 43 | client.select(2, function () { 44 | assert.strictEqual(Object.keys(client.server_info).length, 2, 'Key length should be three'); 45 | assert.strictEqual(typeof client.server_info.db0, 'object', 'db0 keyspace should be an object'); 46 | }); 47 | client.info(['keyspace']); 48 | client.set('foo', 'bar'); 49 | client.info('all', function (err, res) { 50 | assert(Object.keys(client.server_info).length > 3, 'Key length should be way above three'); 51 | assert.strictEqual(typeof client.server_info.redis_version, 'string'); 52 | assert.strictEqual(typeof client.server_info.db2, 'object'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('check redis v.2.4 support', function (done) { 58 | var end = helper.callFuncAfter(done, 2); 59 | client.internal_send_command = function (command_obj) { 60 | assert.strictEqual(command_obj.args.length, 0); 61 | assert.strictEqual(command_obj.command, 'info'); 62 | end(); 63 | }; 64 | client.info(); 65 | client.info(function () {}); 66 | }); 67 | 68 | it('emit error after a failure', function (done) { 69 | client.info(); 70 | client.once('error', function (err) { 71 | assert.strictEqual(err.code, 'UNCERTAIN_STATE'); 72 | assert.strictEqual(err.command, 'INFO'); 73 | done(); 74 | }); 75 | client.stream.destroy(); 76 | }); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/commands/blpop.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var intercept = require('intercept-stdout'); 8 | 9 | describe("The 'blpop' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var client; 15 | var bclient; 16 | 17 | beforeEach(function (done) { 18 | client = redis.createClient.apply(null, args); 19 | client.once('ready', function () { 20 | client.flushdb(done); 21 | }); 22 | }); 23 | 24 | it('pops value immediately if list contains values', function (done) { 25 | bclient = redis.createClient.apply(null, args); 26 | redis.debug_mode = true; 27 | var text = ''; 28 | var unhookIntercept = intercept(function (data) { 29 | text += data; 30 | return ''; 31 | }); 32 | client.rpush('blocking list', 'initial value', helper.isNumber(1)); 33 | unhookIntercept(); 34 | 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)); 35 | redis.debug_mode = false; 36 | bclient.blpop('blocking list', 0, function (err, value) { 37 | assert.strictEqual(value[0], 'blocking list'); 38 | assert.strictEqual(value[1], 'initial value'); 39 | return done(err); 40 | }); 41 | }); 42 | 43 | it('pops value immediately if list contains values using array notation', function (done) { 44 | bclient = redis.createClient.apply(null, args); 45 | client.rpush(['blocking list', 'initial value'], helper.isNumber(1)); 46 | bclient.blpop(['blocking list', 0], function (err, value) { 47 | assert.strictEqual(value[0], 'blocking list'); 48 | assert.strictEqual(value[1], 'initial value'); 49 | return done(err); 50 | }); 51 | }); 52 | 53 | it('waits for value if list is not yet populated', function (done) { 54 | bclient = redis.createClient.apply(null, args); 55 | bclient.blpop('blocking list 2', 5, function (err, value) { 56 | assert.strictEqual(value[0], 'blocking list 2'); 57 | assert.strictEqual(value[1], 'initial value'); 58 | return done(err); 59 | }); 60 | client.rpush('blocking list 2', 'initial value', helper.isNumber(1)); 61 | }); 62 | 63 | it('times out after specified time', function (done) { 64 | bclient = redis.createClient.apply(null, args); 65 | bclient.BLPOP('blocking list', 1, function (err, res) { 66 | assert.strictEqual(res, null); 67 | return done(err); 68 | }); 69 | }); 70 | 71 | afterEach(function () { 72 | client.end(true); 73 | bclient.end(true); 74 | }); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/conect.slave.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('./lib/config'); 5 | var helper = require('./helper'); 6 | var RedisProcess = require('./lib/redis-process'); 7 | var rp; 8 | var path = require('path'); 9 | var redis = config.redis; 10 | 11 | if (process.platform === 'win32') { 12 | // TODO: Fix redis process spawn on windows 13 | return; 14 | } 15 | 16 | describe('master slave sync', function () { 17 | var master = null; 18 | var slave = null; 19 | 20 | before(function (done) { 21 | helper.stopRedis(function () { 22 | helper.startRedis('./conf/password.conf', done); 23 | }); 24 | }); 25 | 26 | before(function (done) { 27 | if (helper.redisProcess().spawnFailed()) return done(); 28 | master = redis.createClient({ 29 | password: 'porkchopsandwiches' 30 | }); 31 | var multi = master.multi(); 32 | var i = 0; 33 | while (i < 1000) { 34 | i++; 35 | // Write some data in the redis instance, so there's something to sync 36 | multi.set('foo' + i, 'bar' + new Array(500).join(Math.random())); 37 | } 38 | multi.exec(done); 39 | }); 40 | 41 | it('sync process and no master should delay ready being emitted for slaves', function (done) { 42 | if (helper.redisProcess().spawnFailed()) this.skip(); 43 | 44 | var port = 6381; 45 | var firstInfo; 46 | slave = redis.createClient({ 47 | port: port, 48 | retry_strategy: function (options) { 49 | // Try to reconnect in very small intervals to catch the master_link_status down before the sync completes 50 | return 10; 51 | } 52 | }); 53 | 54 | var tmp = slave.info.bind(slave); 55 | var i = 0; 56 | slave.info = function (err, res) { 57 | i++; 58 | tmp(err, res); 59 | if (!firstInfo || Object.keys(firstInfo).length === 0) { 60 | firstInfo = slave.server_info; 61 | } 62 | }; 63 | 64 | slave.on('connect', function () { 65 | assert.strictEqual(i, 0); 66 | }); 67 | 68 | var end = helper.callFuncAfter(done, 2); 69 | 70 | slave.on('ready', function () { 71 | assert.strictEqual(this.server_info.master_link_status, 'up'); 72 | assert.strictEqual(firstInfo.master_link_status, 'down'); 73 | assert(i > 1); 74 | this.get('foo300', function (err, res) { 75 | assert.strictEqual(res.substr(0, 3), 'bar'); 76 | end(err); 77 | }); 78 | }); 79 | 80 | RedisProcess.start(function (err, _rp) { 81 | rp = _rp; 82 | end(err); 83 | }, path.resolve(__dirname, './conf/slave.conf'), port); 84 | }); 85 | 86 | after(function (done) { 87 | if (helper.redisProcess().spawnFailed()) return done(); 88 | var end = helper.callFuncAfter(done, 3); 89 | rp.stop(end); 90 | slave.end(true); 91 | master.flushdb(function (err) { 92 | end(err); 93 | master.end(true); 94 | }); 95 | helper.stopRedis(function () { 96 | helper.startRedis('./conf/redis.conf', end); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /benchmarks/diff_multi_bench_output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var metrics = require('metrics'); 5 | // `node diff_multi_bench_output.js beforeBench.txt afterBench.txt` 6 | var file1 = process.argv[2]; 7 | var file2 = process.argv[3]; 8 | 9 | if (!file1 || !file2) { 10 | console.log('Please supply two file arguments:'); 11 | var n = __filename; 12 | n = n.substring(n.lastIndexOf('/', n.length)); 13 | console.log(' node .' + n + ' benchBefore.txt benchAfter.txt\n'); 14 | console.log('To generate the benchmark files, run'); 15 | console.log(' npm run benchmark > benchBefore.txt\n'); 16 | console.log('Thank you for benchmarking responsibly.'); 17 | return; 18 | } 19 | 20 | var before_lines = fs.readFileSync(file1, 'utf8').split('\n'); 21 | var after_lines = fs.readFileSync(file2, 'utf8').split('\n'); 22 | var total_ops = new metrics.Histogram.createUniformHistogram(); 23 | 24 | console.log('Comparing before,', file1, '(', before_lines.length, 'lines)', 'to after,', file2, '(', after_lines.length, 'lines)'); 25 | 26 | function is_whitespace (s) { 27 | return !!s.trim(); 28 | } 29 | 30 | function pad (input, len, chr, right) { 31 | var str = input.toString(); 32 | chr = chr || ' '; 33 | 34 | if (right) { 35 | while (str.length < len) { 36 | str += chr; 37 | } 38 | } else { 39 | while (str.length < len) { 40 | str = chr + str; 41 | } 42 | } 43 | return str; 44 | } 45 | 46 | // green if greater than 0, red otherwise 47 | function humanize_diff (num, unit, toFixed) { 48 | unit = unit || ''; 49 | if (num > 0) { 50 | return ' +' + pad(num.toFixed(toFixed || 0) + unit, 7); 51 | } 52 | return ' -' + pad(Math.abs(num).toFixed(toFixed || 0) + unit, 7); 53 | } 54 | 55 | function command_name (words) { 56 | var line = words.join(' '); 57 | return line.substr(0, line.indexOf(',')); 58 | } 59 | 60 | before_lines.forEach(function (b, i) { 61 | var a = after_lines[i]; 62 | if (!a || !b || !b.trim() || !a.trim()) { 63 | // console.log('#ignored#', '>'+a+'<', '>'+b+'<'); 64 | return; 65 | } 66 | var b_words = b.split(' ').filter(is_whitespace); 67 | var a_words = a.split(' ').filter(is_whitespace); 68 | 69 | var ops = [b_words, a_words].map(function (words) { 70 | // console.log(words); 71 | return words.slice(-2, -1) | 0; 72 | }).filter(function (num) { 73 | var isNaN = !num && num !== 0; 74 | return !isNaN; 75 | }); 76 | if (ops.length !== 2) { 77 | return; 78 | } 79 | var delta = ops[1] - ops[0]; 80 | var pct = +((delta / ops[0]) * 100); 81 | ops[0] = pad(ops[0], 6); 82 | ops[1] = pad(ops[1], 6); 83 | total_ops.update(delta); 84 | delta = humanize_diff(delta); 85 | var small_delta = pct < 3 && pct > -3; 86 | // Let's mark differences above 20% bold 87 | var big_delta = pct > 20 || pct < -20 ? ';1' : ''; 88 | pct = humanize_diff(pct, '', 2) + '%'; 89 | var str = pad((command_name(a_words) === command_name(b_words) ? command_name(a_words) + ':' : '404:'), 14, false, true) + 90 | (pad(ops.join(' -> '), 15) + ' ops/sec (∆' + delta + pct + ')'); 91 | str = (small_delta ? '' : (/-[^>]/.test(str) ? '\x1b[31' : '\x1b[32') + big_delta + 'm') + str + '\x1b[0m'; 92 | console.log(str); 93 | }); 94 | 95 | console.log('Mean difference in ops/sec:', humanize_diff(total_ops.mean(), '', 1)); 96 | -------------------------------------------------------------------------------- /test/commands/mget.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'mget' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient.apply(null, args); 17 | client.once('error', done); 18 | client.once('ready', function () { 19 | client.flushdb(); 20 | client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], done); 21 | }); 22 | }); 23 | 24 | it('handles fetching multiple keys in argument form', function (done) { 25 | client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], helper.isString('OK')); 26 | client.MGET('mget keys 1', 'mget keys 2', 'mget keys 3', function (err, results) { 27 | assert.strictEqual(3, results.length); 28 | assert.strictEqual('mget val 1', results[0].toString()); 29 | assert.strictEqual('mget val 2', results[1].toString()); 30 | assert.strictEqual('mget val 3', results[2].toString()); 31 | return done(err); 32 | }); 33 | }); 34 | 35 | it('handles fetching multiple keys via an array', function (done) { 36 | client.mget(['mget keys 1', 'mget keys 2', 'mget keys 3'], function (err, results) { 37 | assert.strictEqual('mget val 1', results[0].toString()); 38 | assert.strictEqual('mget val 2', results[1].toString()); 39 | assert.strictEqual('mget val 3', results[2].toString()); 40 | return done(err); 41 | }); 42 | }); 43 | 44 | it('handles fetching multiple keys, when some keys do not exist', function (done) { 45 | client.MGET('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3'], function (err, results) { 46 | assert.strictEqual(4, results.length); 47 | assert.strictEqual('mget val 1', results[0].toString()); 48 | assert.strictEqual(null, results[1]); 49 | assert.strictEqual('mget val 2', results[2].toString()); 50 | assert.strictEqual('mget val 3', results[3].toString()); 51 | return done(err); 52 | }); 53 | }); 54 | 55 | it('handles fetching multiple keys, when some keys do not exist promisified', function () { 56 | return client.MGETAsync('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3']).then(function (results) { 57 | assert.strictEqual(4, results.length); 58 | assert.strictEqual('mget val 1', results[0].toString()); 59 | assert.strictEqual(null, results[1]); 60 | assert.strictEqual('mget val 2', results[2].toString()); 61 | assert.strictEqual('mget val 3', results[3].toString()); 62 | }); 63 | }); 64 | 65 | afterEach(function () { 66 | client.end(true); 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/lib/redis-process.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // helper to start and stop the redis process. 4 | var config = require('./config'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var spawn = require('win-spawn'); 8 | var tcpPortUsed = require('tcp-port-used'); 9 | var bluebird = require('bluebird'); 10 | 11 | // wait for redis to be listening in 12 | // all three modes (ipv4, ipv6, socket). 13 | function waitForRedis (available, cb, port) { 14 | if (process.platform === 'win32') return cb(); 15 | 16 | var time = Date.now(); 17 | var running = false; 18 | var socket = '/tmp/redis.sock'; 19 | if (port) { 20 | // We have to distinguishe the redis sockets if we have more than a single redis instance running 21 | socket = '/tmp/redis' + port + '.sock'; 22 | } 23 | port = port || config.PORT; 24 | var id = setInterval(function () { 25 | if (running) return; 26 | running = true; 27 | bluebird.join( 28 | tcpPortUsed.check(port, '127.0.0.1'), 29 | tcpPortUsed.check(port, '::1'), 30 | function (ipV4, ipV6) { 31 | if (ipV6 === available && ipV4 === available) { 32 | if (fs.existsSync(socket) === available) { 33 | clearInterval(id); 34 | return cb(); 35 | } 36 | // The same message applies for can't stop but we ignore that case 37 | throw new Error('Port ' + port + ' is already in use. Tests can\'t start.\n'); 38 | } 39 | if (Date.now() - time > 6000) { 40 | throw new Error('Redis could not start on port ' + (port || config.PORT) + '\n'); 41 | } 42 | running = false; 43 | } 44 | ).catch(function (err) { 45 | console.error('\x1b[31m' + err.stack + '\x1b[0m\n'); 46 | process.exit(1); 47 | }); 48 | }, 100); 49 | } 50 | 51 | module.exports = { 52 | start: function (done, conf, port) { 53 | var spawnFailed = false; 54 | // spawn redis with our testing configuration. 55 | var confFile = conf || path.resolve(__dirname, '../conf/redis.conf'); 56 | var rp = spawn('redis-server', [confFile], {}); 57 | 58 | // capture a failure booting redis, and give 59 | // the user running the test some directions. 60 | rp.once('exit', function (code) { 61 | if (code !== 0) spawnFailed = true; 62 | }); 63 | 64 | // wait for redis to become available, by 65 | // checking the port we bind on. 66 | waitForRedis(true, function () { 67 | // return an object that can be used in 68 | // an after() block to shutdown redis. 69 | return done(null, { 70 | spawnFailed: function () { 71 | return spawnFailed; 72 | }, 73 | stop: function (done) { 74 | if (spawnFailed) return done(); 75 | rp.once('exit', function (code) { 76 | var error = null; 77 | if (code !== null && code !== 0) { 78 | error = new Error('Redis shutdown failed with code ' + code); 79 | } 80 | waitForRedis(false, function () { 81 | return done(error); 82 | }, port); 83 | }); 84 | rp.kill('SIGTERM'); 85 | } 86 | }); 87 | }, port); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /test/commands/get.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var uuid = require('uuid'); 8 | 9 | describe("The 'get' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var key, value; 15 | 16 | beforeEach(function () { 17 | key = uuid.v4(); 18 | value = uuid.v4(); 19 | }); 20 | 21 | describe('when not connected', function () { 22 | var client; 23 | 24 | beforeEach(function (done) { 25 | client = redis.createClient.apply(null, args); 26 | client.once('ready', function () { 27 | client.quit(); 28 | }); 29 | client.on('end', done); 30 | }); 31 | 32 | it('reports an error', function (done) { 33 | client.get(key, function (err, res) { 34 | assert(err.message.match(/The connection is already closed/)); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('reports an error promisified', function () { 40 | return client.getAsync(key).then(assert, function (err) { 41 | assert(err.message.match(/The connection is already closed/)); 42 | }); 43 | }); 44 | }); 45 | 46 | describe('when connected', function () { 47 | var client; 48 | 49 | beforeEach(function (done) { 50 | client = redis.createClient.apply(null, args); 51 | client.once('ready', function () { 52 | done(); 53 | }); 54 | }); 55 | 56 | afterEach(function () { 57 | client.end(true); 58 | }); 59 | 60 | describe('when the key exists in Redis', function () { 61 | beforeEach(function (done) { 62 | client.set(key, value, function (err, res) { 63 | helper.isNotError()(err, res); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('gets the value correctly', function (done) { 69 | client.GET(key, function (err, res) { 70 | helper.isString(value)(err, res); 71 | done(err); 72 | }); 73 | }); 74 | 75 | it("should not throw on a get without callback (even if it's not useful)", function (done) { 76 | client.GET(key); 77 | client.on('error', function (err) { 78 | throw err; 79 | }); 80 | setTimeout(done, 25); 81 | }); 82 | }); 83 | 84 | describe('when the key does not exist in Redis', function () { 85 | it('gets a null value', function (done) { 86 | client.get(key, function (err, res) { 87 | helper.isNull()(err, res); 88 | done(err); 89 | }); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /lib/createClient.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var URL = require('url'); 5 | 6 | module.exports = function createClient (port_arg, host_arg, options) { 7 | 8 | if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) { 9 | 10 | var host; 11 | if (typeof host_arg === 'string') { 12 | host = host_arg; 13 | } else { 14 | if (options && host_arg) { 15 | throw new TypeError('Unknown type of connection in createClient()'); 16 | } 17 | options = options || host_arg; 18 | } 19 | options = utils.clone(options); 20 | options.host = host || options.host; 21 | options.port = port_arg; 22 | 23 | } else if (typeof port_arg === 'string' || port_arg && port_arg.url) { 24 | 25 | options = utils.clone(port_arg.url ? port_arg : host_arg || options); 26 | var url = port_arg.url || port_arg; 27 | var parsed = URL.parse(url, true, true); 28 | 29 | // [redis:]//[[user][:password]@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]] 30 | if (parsed.slashes) { // We require slashes 31 | if (parsed.auth) { 32 | options.password = parsed.auth.slice(parsed.auth.indexOf(':') + 1); 33 | } 34 | if (parsed.protocol) { 35 | if (parsed.protocol === 'rediss:') { 36 | options.tls = options.tls || {}; 37 | } else if (parsed.protocol !== 'redis:') { 38 | console.warn('node_redis: WARNING: You passed "' + parsed.protocol.substring(0, parsed.protocol.length - 1) + '" as protocol instead of the "redis" protocol!'); 39 | } 40 | } 41 | if (parsed.pathname && parsed.pathname !== '/') { 42 | options.db = parsed.pathname.substr(1); 43 | } 44 | if (parsed.hostname) { 45 | options.host = parsed.hostname; 46 | } 47 | if (parsed.port) { 48 | options.port = parsed.port; 49 | } 50 | if (parsed.search !== '') { 51 | var elem; 52 | for (elem in parsed.query) { 53 | // If options are passed twice, only the parsed options will be used 54 | if (elem in options) { 55 | if (options[elem] === parsed.query[elem]) { 56 | console.warn('node_redis: WARNING: You passed the ' + elem + ' option twice!'); 57 | } else { 58 | throw new RangeError('The ' + elem + ' option is added twice and does not match'); 59 | } 60 | } 61 | options[elem] = parsed.query[elem]; 62 | } 63 | } 64 | } else if (parsed.hostname) { 65 | throw new RangeError('The redis url must begin with slashes "//" or contain slashes after the redis protocol'); 66 | } else { 67 | options.path = url; 68 | } 69 | 70 | } else if (typeof port_arg === 'object' || port_arg === undefined) { 71 | options = utils.clone(port_arg || options); 72 | options.host = options.host || host_arg; 73 | 74 | if (port_arg && arguments.length !== 1) { 75 | throw new TypeError('Too many arguments passed to createClient. Please only pass the options object'); 76 | } 77 | } 78 | 79 | if (!options) { 80 | throw new TypeError('Unknown type of connection in createClient()'); 81 | } 82 | 83 | return options; 84 | }; 85 | -------------------------------------------------------------------------------- /test/commands/dbsize.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var uuid = require('uuid'); 8 | 9 | describe("The 'dbsize' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var key, value; 15 | 16 | beforeEach(function () { 17 | key = uuid.v4(); 18 | value = uuid.v4(); 19 | }); 20 | 21 | describe('when not connected', function () { 22 | var client; 23 | 24 | beforeEach(function (done) { 25 | client = redis.createClient.apply(null, args); 26 | client.once('ready', function () { 27 | client.quit(); 28 | }); 29 | client.on('end', done); 30 | }); 31 | 32 | it('reports an error', function (done) { 33 | client.dbsize([], function (err, res) { 34 | assert(err.message.match(/The connection is already closed/)); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | 40 | describe('when connected', function () { 41 | var client; 42 | 43 | beforeEach(function (done) { 44 | client = redis.createClient.apply(null, args); 45 | client.once('ready', function () { 46 | client.flushdb(function (err, res) { 47 | helper.isString('OK')(err, res); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | 53 | afterEach(function () { 54 | client.end(true); 55 | }); 56 | 57 | it('returns a zero db size', function (done) { 58 | client.DBSIZE([], function (err, res) { 59 | helper.isNotError()(err, res); 60 | helper.isType.number()(err, res); 61 | assert.strictEqual(res, 0, 'Initial db size should be 0'); 62 | done(); 63 | }); 64 | }); 65 | 66 | describe('when more data is added to Redis', function () { 67 | var oldSize; 68 | 69 | beforeEach(function (done) { 70 | client.dbsize(function (err, res) { 71 | helper.isType.number()(err, res); 72 | assert.strictEqual(res, 0, 'Initial db size should be 0'); 73 | 74 | oldSize = res; 75 | 76 | client.set(key, value, function (err, res) { 77 | helper.isNotError()(err, res); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | 83 | it('returns a larger db size', function (done) { 84 | client.dbsize([], function (err, res) { 85 | helper.isNotError()(err, res); 86 | helper.isType.positiveNumber()(err, res); 87 | assert.strictEqual(true, (oldSize < res), 'Adding data should increase db size.'); 88 | done(); 89 | }); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/custom_errors.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var errors = require('../lib/customErrors'); 5 | 6 | describe('errors', function () { 7 | 8 | describe('AbortError', function () { 9 | it('should inherit from Error', function () { 10 | var e = new errors.AbortError({}); 11 | assert.strictEqual(e.message, ''); 12 | assert.strictEqual(e.name, 'AbortError'); 13 | assert.strictEqual(Object.keys(e).length, 0); 14 | assert(e instanceof Error); 15 | assert(e instanceof errors.AbortError); 16 | }); 17 | 18 | it('should list options properties but not name and message', function () { 19 | var e = new errors.AbortError({ 20 | name: 'weird', 21 | message: 'hello world', 22 | property: true 23 | }); 24 | assert.strictEqual(e.message, 'hello world'); 25 | assert.strictEqual(e.name, 'weird'); 26 | assert.strictEqual(e.property, true); 27 | assert.strictEqual(Object.keys(e).length, 2); 28 | assert(e instanceof Error); 29 | assert(e instanceof errors.AbortError); 30 | assert(delete e.name); 31 | assert.strictEqual(e.name, 'AbortError'); 32 | }); 33 | 34 | it('should change name and message', function () { 35 | var e = new errors.AbortError({ 36 | message: 'hello world', 37 | property: true 38 | }); 39 | assert.strictEqual(e.name, 'AbortError'); 40 | assert.strictEqual(e.message, 'hello world'); 41 | e.name = 'foo'; 42 | e.message = 'foobar'; 43 | assert.strictEqual(e.name, 'foo'); 44 | assert.strictEqual(e.message, 'foobar'); 45 | }); 46 | }); 47 | 48 | describe('AggregateError', function () { 49 | it('should inherit from Error and AbortError', function () { 50 | var e = new errors.AggregateError({}); 51 | assert.strictEqual(e.message, ''); 52 | assert.strictEqual(e.name, 'AggregateError'); 53 | assert.strictEqual(Object.keys(e).length, 0); 54 | assert(e instanceof Error); 55 | assert(e instanceof errors.AggregateError); 56 | assert(e instanceof errors.AbortError); 57 | }); 58 | 59 | it('should list options properties but not name and message', function () { 60 | var e = new errors.AggregateError({ 61 | name: 'weird', 62 | message: 'hello world', 63 | property: true 64 | }); 65 | assert.strictEqual(e.message, 'hello world'); 66 | assert.strictEqual(e.name, 'weird'); 67 | assert.strictEqual(e.property, true); 68 | assert.strictEqual(Object.keys(e).length, 2); 69 | assert(e instanceof Error); 70 | assert(e instanceof errors.AggregateError); 71 | assert(e instanceof errors.AbortError); 72 | assert(delete e.name); 73 | assert.strictEqual(e.name, 'AggregateError'); 74 | }); 75 | 76 | it('should change name and message', function () { 77 | var e = new errors.AggregateError({ 78 | message: 'hello world', 79 | property: true 80 | }); 81 | assert.strictEqual(e.name, 'AggregateError'); 82 | assert.strictEqual(e.message, 'hello world'); 83 | e.name = 'foo'; 84 | e.message = 'foobar'; 85 | assert.strictEqual(e.name, 'foo'); 86 | assert.strictEqual(e.message, 'foobar'); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/commands/hgetall.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'hgetall' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | 15 | describe('regular client', function () { 16 | 17 | beforeEach(function (done) { 18 | client = redis.createClient.apply(null, args); 19 | client.once('ready', function () { 20 | client.flushdb(done); 21 | }); 22 | }); 23 | 24 | it('handles simple keys and values', function (done) { 25 | client.hmset(['hosts', 'hasOwnProperty', '1', 'another', '23', 'home', '1234'], helper.isString('OK')); 26 | client.HGETALL(['hosts'], function (err, obj) { 27 | assert.strictEqual(3, Object.keys(obj).length); 28 | assert.strictEqual('1', obj.hasOwnProperty.toString()); 29 | assert.strictEqual('23', obj.another.toString()); 30 | assert.strictEqual('1234', obj.home.toString()); 31 | done(err); 32 | }); 33 | }); 34 | 35 | it('handles fetching keys set using an object', function (done) { 36 | client.batch().HMSET('msg_test', { message: 'hello' }, undefined).exec(); 37 | client.hgetall('msg_test', function (err, obj) { 38 | assert.strictEqual(1, Object.keys(obj).length); 39 | assert.strictEqual(obj.message, 'hello'); 40 | done(err); 41 | }); 42 | }); 43 | 44 | it('handles fetching a messing key', function (done) { 45 | client.hgetall('missing', function (err, obj) { 46 | assert.strictEqual(null, obj); 47 | done(err); 48 | }); 49 | }); 50 | }); 51 | 52 | describe('binary client', function () { 53 | var client; 54 | var args = config.configureClient(parser, ip, { 55 | return_buffers: true 56 | }); 57 | 58 | beforeEach(function (done) { 59 | client = redis.createClient.apply(null, args); 60 | client.once('ready', function () { 61 | client.flushdb(done); 62 | }); 63 | }); 64 | 65 | it('returns binary results', function (done) { 66 | client.hmset(['bhosts', 'mjr', '1', 'another', '23', 'home', '1234', new Buffer([0xAA, 0xBB, 0x00, 0xF0]), new Buffer([0xCC, 0xDD, 0x00, 0xF0])], helper.isString('OK')); 67 | client.HGETALL('bhosts', function (err, obj) { 68 | assert.strictEqual(4, Object.keys(obj).length); 69 | assert.strictEqual('1', obj.mjr.toString()); 70 | assert.strictEqual('23', obj.another.toString()); 71 | assert.strictEqual('1234', obj.home.toString()); 72 | assert.strictEqual((new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary'), Object.keys(obj)[3]); 73 | assert.strictEqual((new Buffer([0xCC, 0xDD, 0x00, 0xF0])).toString('binary'), obj[(new Buffer([0xAA, 0xBB, 0x00, 0xF0])).toString('binary')].toString('binary')); 74 | return done(err); 75 | }); 76 | }); 77 | }); 78 | 79 | afterEach(function () { 80 | client.end(true); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/commands/hset.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'hset' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | var hash = 'test hash'; 15 | 16 | beforeEach(function (done) { 17 | client = redis.createClient.apply(null, args); 18 | client.once('ready', function () { 19 | client.flushdb(done); 20 | }); 21 | }); 22 | 23 | it('allows a value to be set in a hash', function (done) { 24 | var field = new Buffer('0123456789'); 25 | var value = new Buffer('abcdefghij'); 26 | 27 | client.hset(hash, field, value, helper.isNumber(1)); 28 | client.HGET(hash, field, helper.isString(value.toString(), done)); 29 | }); 30 | 31 | it('handles an empty value', function (done) { 32 | var field = new Buffer('0123456789'); 33 | var value = new Buffer(0); 34 | 35 | client.HSET(hash, field, value, helper.isNumber(1)); 36 | client.HGET([hash, field], helper.isString('', done)); 37 | }); 38 | 39 | it('handles empty key and value', function (done) { 40 | var field = new Buffer(0); 41 | var value = new Buffer(0); 42 | client.HSET([hash, field, value], function (err, res) { 43 | assert.strictEqual(res, 1); 44 | client.HSET(hash, field, value, helper.isNumber(0, done)); 45 | }); 46 | }); 47 | 48 | it('warns if someone passed a array either as field or as value', function (done) { 49 | var hash = 'test hash'; 50 | var field = 'array'; 51 | // This would be converted to "array contents" but if you use more than one entry, 52 | // it'll result in e.g. "array contents,second content" and this is not supported and considered harmful 53 | var value = ['array contents']; 54 | client.on('warning', function (msg) { 55 | assert.strictEqual( 56 | msg, 57 | 'Deprecated: The HMSET command contains a argument of type Array.\n' + 58 | 'This is converted to "array contents" by using .toString() now and will return an error from v.3.0 on.\n' + 59 | 'Please handle this in your code to make sure everything works as you intended it to.' 60 | ); 61 | done(); 62 | }); 63 | client.HMSET(hash, field, value); 64 | }); 65 | 66 | it('does not error when a buffer and date are set as values on the same hash', function (done) { 67 | var hash = 'test hash'; 68 | var field1 = 'buffer'; 69 | var value1 = new Buffer('abcdefghij'); 70 | var field2 = 'date'; 71 | var value2 = new Date(); 72 | 73 | client.HMSET(hash, field1, value1, field2, value2, helper.isString('OK', done)); 74 | }); 75 | 76 | it('does not error when a buffer and date are set as fields on the same hash', function (done) { 77 | var hash = 'test hash'; 78 | var value1 = 'buffer'; 79 | var field1 = new Buffer('abcdefghij'); 80 | var value2 = 'date'; 81 | var field2 = new Date(); 82 | 83 | client.HMSET(hash, field1, value1, field2, value2, helper.isString('OK', done)); 84 | }); 85 | 86 | afterEach(function () { 87 | client.end(true); 88 | }); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/commands/incr.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'incr' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | 14 | describe('when connected and a value in Redis', function () { 15 | 16 | var client; 17 | var key = 'ABOVE_SAFE_JAVASCRIPT_INTEGER'; 18 | var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // Backwards compatible 19 | 20 | afterEach(function () { 21 | client.end(true); 22 | }); 23 | 24 | /* 25 | Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991 26 | 27 | 9007199254740992 -> 9007199254740992 28 | 9007199254740993 -> 9007199254740992 29 | 9007199254740994 -> 9007199254740994 30 | 9007199254740995 -> 9007199254740996 31 | 9007199254740996 -> 9007199254740996 32 | 9007199254740997 -> 9007199254740996 33 | ... 34 | */ 35 | it('count above the safe integers as numbers', function (done) { 36 | client = redis.createClient.apply(null, args); 37 | // Set a value to the maximum safe allowed javascript number (2^53) - 1 38 | client.set(key, MAX_SAFE_INTEGER, helper.isNotError()); 39 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 1)); 40 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 2)); 41 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 3)); 42 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 4)); 43 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 5)); 44 | client.INCR(key, function (err, res) { 45 | helper.isNumber(MAX_SAFE_INTEGER + 6)(err, res); 46 | assert.strictEqual(typeof res, 'number'); 47 | }); 48 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 7)); 49 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 8)); 50 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 9)); 51 | client.INCR(key, helper.isNumber(MAX_SAFE_INTEGER + 10, done)); 52 | }); 53 | 54 | it('count above the safe integers as strings', function (done) { 55 | args[2].string_numbers = true; 56 | client = redis.createClient.apply(null, args); 57 | // Set a value to the maximum safe allowed javascript number (2^53) 58 | client.set(key, MAX_SAFE_INTEGER, helper.isNotError()); 59 | client.incr(key, helper.isString('9007199254740992')); 60 | client.incr(key, helper.isString('9007199254740993')); 61 | client.incr(key, helper.isString('9007199254740994')); 62 | client.incr(key, helper.isString('9007199254740995')); 63 | client.incr(key, helper.isString('9007199254740996')); 64 | client.incr(key, function (err, res) { 65 | helper.isString('9007199254740997')(err, res); 66 | assert.strictEqual(typeof res, 'string'); 67 | }); 68 | client.incr(key, helper.isString('9007199254740998')); 69 | client.incr(key, helper.isString('9007199254740999')); 70 | client.incr(key, helper.isString('9007199254741000')); 71 | client.incr(key, helper.isString('9007199254741001', done)); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/commands/getset.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var uuid = require('uuid'); 8 | 9 | describe("The 'getset' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var key, value, value2; 15 | 16 | beforeEach(function () { 17 | key = uuid.v4(); 18 | value = uuid.v4(); 19 | value2 = uuid.v4(); 20 | }); 21 | 22 | describe('when not connected', function () { 23 | var client; 24 | 25 | beforeEach(function (done) { 26 | client = redis.createClient.apply(null, args); 27 | client.once('ready', function () { 28 | client.quit(); 29 | }); 30 | client.on('end', done); 31 | }); 32 | 33 | it('reports an error', function (done) { 34 | client.get(key, function (err, res) { 35 | assert(err.message.match(/The connection is already closed/)); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('when connected', function () { 42 | var client; 43 | 44 | beforeEach(function (done) { 45 | client = redis.createClient.apply(null, args); 46 | client.once('ready', function () { 47 | done(); 48 | }); 49 | }); 50 | 51 | afterEach(function () { 52 | client.end(true); 53 | }); 54 | 55 | describe('when the key exists in Redis', function () { 56 | beforeEach(function (done) { 57 | client.set(key, value, function (err, res) { 58 | helper.isNotError()(err, res); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('gets the value correctly', function (done) { 64 | client.GETSET(key, value2, function (err, res) { 65 | helper.isString(value)(err, res); 66 | client.get(key, function (err, res) { 67 | helper.isString(value2)(err, res); 68 | done(err); 69 | }); 70 | }); 71 | }); 72 | 73 | it('gets the value correctly with array syntax', function (done) { 74 | client.GETSET([key, value2], function (err, res) { 75 | helper.isString(value)(err, res); 76 | client.get(key, function (err, res) { 77 | helper.isString(value2)(err, res); 78 | done(err); 79 | }); 80 | }); 81 | }); 82 | 83 | it('gets the value correctly with array syntax style 2', function (done) { 84 | client.GETSET(key, [value2], function (err, res) { 85 | helper.isString(value)(err, res); 86 | client.get(key, function (err, res) { 87 | helper.isString(value2)(err, res); 88 | done(err); 89 | }); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('when the key does not exist in Redis', function () { 95 | it('gets a null value', function (done) { 96 | client.getset(key, value, function (err, res) { 97 | helper.isNull()(err, res); 98 | done(err); 99 | }); 100 | }); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/commands/flushdb.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var uuid = require('uuid'); 8 | 9 | describe("The 'flushdb' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var key, key2; 15 | 16 | beforeEach(function () { 17 | key = uuid.v4(); 18 | key2 = uuid.v4(); 19 | }); 20 | 21 | describe('when not connected', function () { 22 | var client; 23 | 24 | beforeEach(function (done) { 25 | client = redis.createClient.apply(null, args); 26 | client.once('ready', function () { 27 | client.quit(); 28 | }); 29 | client.on('end', done); 30 | }); 31 | 32 | it('reports an error', function (done) { 33 | client.flushdb(function (err, res) { 34 | assert(err.message.match(/The connection is already closed/)); 35 | done(); 36 | }); 37 | }); 38 | }); 39 | 40 | describe('when connected', function () { 41 | var client; 42 | 43 | beforeEach(function (done) { 44 | client = redis.createClient.apply(null, args); 45 | client.once('ready', function () { 46 | done(); 47 | }); 48 | }); 49 | 50 | afterEach(function () { 51 | client.end(true); 52 | }); 53 | 54 | describe('when there is data in Redis', function () { 55 | 56 | beforeEach(function (done) { 57 | client.mset(key, uuid.v4(), key2, uuid.v4(), helper.isNotError()); 58 | client.dbsize([], function (err, res) { 59 | helper.isType.positiveNumber()(err, res); 60 | assert.equal(res, 2, 'Two keys should have been inserted'); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('deletes all the keys', function (done) { 66 | client.flushdb(function (err, res) { 67 | assert.equal(res, 'OK'); 68 | client.mget(key, key2, function (err, res) { 69 | assert.strictEqual(null, err, 'Unexpected error returned'); 70 | assert.strictEqual(true, Array.isArray(res), 'Results object should be an array.'); 71 | assert.strictEqual(2, res.length, 'Results array should have length 2.'); 72 | assert.strictEqual(null, res[0], 'Redis key should have been flushed.'); 73 | assert.strictEqual(null, res[1], 'Redis key should have been flushed.'); 74 | done(err); 75 | }); 76 | }); 77 | }); 78 | 79 | it('results in a db size of zero', function (done) { 80 | client.flushdb(function (err, res) { 81 | client.dbsize([], function (err, res) { 82 | helper.isNotError()(err, res); 83 | helper.isType.number()(err, res); 84 | assert.strictEqual(0, res, 'Flushing db should result in db size 0'); 85 | done(); 86 | }); 87 | }); 88 | }); 89 | 90 | it('results in a db size of zero without a callback', function (done) { 91 | client.flushdb(); 92 | setTimeout(function (err, res) { 93 | client.dbsize(function (err, res) { 94 | helper.isNotError()(err, res); 95 | helper.isType.number()(err, res); 96 | assert.strictEqual(0, res, 'Flushing db should result in db size 0'); 97 | done(); 98 | }); 99 | }, 25); 100 | }); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /lib/extendedApi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var debug = require('./debug'); 5 | var RedisClient = require('../').RedisClient; 6 | var Command = require('./command'); 7 | var noop = function () {}; 8 | 9 | /********************************************** 10 | All documented and exposed API belongs in here 11 | **********************************************/ 12 | 13 | // Redirect calls to the appropriate function and use to send arbitrary / not supported commands 14 | RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) { 15 | // Throw to fail early instead of relying in order in this case 16 | if (typeof command !== 'string') { 17 | throw new TypeError('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name'); 18 | } 19 | command = command.toLowerCase(); 20 | if (!Array.isArray(args)) { 21 | if (args === undefined || args === null) { 22 | args = []; 23 | } else if (typeof args === 'function' && callback === undefined) { 24 | callback = args; 25 | args = []; 26 | } else { 27 | throw new TypeError('Wrong input type "' + args.constructor.name + '" for args'); 28 | } 29 | } 30 | if (typeof callback !== 'function' && callback !== undefined) { 31 | throw new TypeError('Wrong input type "' + (callback !== null ? callback.constructor.name : 'null') + '" for callback function'); 32 | } 33 | 34 | // Using the raw multi command is only possible with this function 35 | // If the command is not yet added to the client, the internal function should be called right away 36 | // Otherwise we need to redirect the calls to make sure the internal functions don't get skipped 37 | // The internal functions could actually be used for any non hooked function 38 | // but this might change from time to time and at the moment there's no good way to distinguish them 39 | // from each other, so let's just do it do it this way for the time being 40 | if (command === 'multi' || typeof this[command] !== 'function') { 41 | return this.internal_send_command(new Command(command, args, callback)); 42 | } 43 | if (typeof callback === 'function') { 44 | args = args.concat([callback]); // Prevent manipulating the input array 45 | } 46 | return this[command].apply(this, args); 47 | }; 48 | 49 | RedisClient.prototype.end = function (flush) { 50 | // Flush queue if wanted 51 | if (flush) { 52 | this.flush_and_error({ 53 | message: 'Connection forcefully ended and command aborted.', 54 | code: 'NR_CLOSED' 55 | }); 56 | } else if (arguments.length === 0) { 57 | this.warn( 58 | 'Using .end() without the flush parameter is deprecated and throws from v.3.0.0 on.\n' + 59 | 'Please check the doku (https://github.com/NodeRedis/node_redis) and explictly use flush.' 60 | ); 61 | } 62 | // Clear retry_timer 63 | if (this.retry_timer) { 64 | clearTimeout(this.retry_timer); 65 | this.retry_timer = null; 66 | } 67 | this.stream.removeAllListeners(); 68 | this.stream.on('error', noop); 69 | this.connected = false; 70 | this.ready = false; 71 | this.closing = true; 72 | return this.stream.destroySoon(); 73 | }; 74 | 75 | RedisClient.prototype.unref = function () { 76 | if (this.connected) { 77 | debug("Unref'ing the socket connection"); 78 | this.stream.unref(); 79 | } else { 80 | debug('Not connected yet, will unref later'); 81 | this.once('connect', function () { 82 | this.unref(); 83 | }); 84 | } 85 | }; 86 | 87 | RedisClient.prototype.duplicate = function (options, callback) { 88 | if (typeof options === 'function') { 89 | callback = options; 90 | options = null; 91 | } 92 | var existing_options = utils.clone(this.options); 93 | options = utils.clone(options); 94 | for (var elem in options) { 95 | existing_options[elem] = options[elem]; 96 | } 97 | var client = new RedisClient(existing_options); 98 | client.selected_db = options.db || this.selected_db; 99 | if (typeof callback === 'function') { 100 | var ready_listener = function () { 101 | callback(null, client); 102 | client.removeAllListeners(error_listener); 103 | }; 104 | var error_listener = function (err) { 105 | callback(err); 106 | client.end(true); 107 | }; 108 | client.once('ready', ready_listener); 109 | client.once('error', error_listener); 110 | return; 111 | } 112 | return client; 113 | }; 114 | -------------------------------------------------------------------------------- /test/commands/mset.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | var uuid = require('uuid'); 8 | 9 | describe("The 'mset' method", function () { 10 | 11 | helper.allTests(function (parser, ip, args) { 12 | 13 | describe('using ' + parser + ' and ' + ip, function () { 14 | var key, value, key2, value2; 15 | 16 | beforeEach(function () { 17 | key = uuid.v4(); 18 | value = uuid.v4(); 19 | key2 = uuid.v4(); 20 | value2 = uuid.v4(); 21 | }); 22 | 23 | describe('when not connected', function () { 24 | var client; 25 | 26 | beforeEach(function (done) { 27 | client = redis.createClient.apply(null, args); 28 | client.once('ready', function () { 29 | client.quit(); 30 | }); 31 | client.on('end', done); 32 | }); 33 | 34 | it('reports an error', function (done) { 35 | client.mset(key, value, key2, value2, function (err, res) { 36 | assert(err.message.match(/The connection is already closed/)); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | describe('when connected', function () { 43 | var client; 44 | 45 | beforeEach(function (done) { 46 | client = redis.createClient.apply(null, args); 47 | client.once('ready', function () { 48 | done(); 49 | }); 50 | }); 51 | 52 | afterEach(function () { 53 | client.end(true); 54 | }); 55 | 56 | describe('and a callback is specified', function () { 57 | describe('with valid parameters', function () { 58 | it('sets the value correctly', function (done) { 59 | client.mset(key, value, key2, value2, function (err) { 60 | if (err) { 61 | return done(err); 62 | } 63 | client.get(key, helper.isString(value)); 64 | client.get(key2, helper.isString(value2, done)); 65 | }); 66 | }); 67 | }); 68 | 69 | describe("with undefined 'key' parameter and missing 'value' parameter", function () { 70 | it('reports an error', function (done) { 71 | client.mset(undefined, function (err, res) { 72 | helper.isError()(err, null); 73 | done(); 74 | }); 75 | }); 76 | }); 77 | 78 | }); 79 | 80 | describe('and no callback is specified', function () { 81 | describe('with valid parameters', function () { 82 | it('sets the value correctly', function (done) { 83 | client.mset(key, value2, key2, value); 84 | client.get(key, helper.isString(value2)); 85 | client.get(key2, helper.isString(value, done)); 86 | }); 87 | 88 | it('sets the value correctly with array syntax', function (done) { 89 | client.mset([key, value2, key2, value]); 90 | client.get(key, helper.isString(value2)); 91 | client.get(key2, helper.isString(value, done)); 92 | }); 93 | }); 94 | 95 | describe("with undefined 'key' and missing 'value' parameter", function () { 96 | // this behavior is different from the 'set' behavior. 97 | it('emits an error', function (done) { 98 | client.on('error', function (err) { 99 | assert.strictEqual(err.message, "ERR wrong number of arguments for 'mset' command"); 100 | assert.strictEqual(err.name, 'ReplyError'); 101 | done(); 102 | }); 103 | 104 | client.mset(); 105 | }); 106 | }); 107 | }); 108 | }); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // hgetall converts its replies to an Object. If the reply is empty, null is returned. 4 | // These function are only called with internal data and have therefore always the same instanceof X 5 | function replyToObject (reply) { 6 | // The reply might be a string or a buffer if this is called in a transaction (multi) 7 | if (reply.length === 0 || !(reply instanceof Array)) { 8 | return null; 9 | } 10 | var obj = {}; 11 | for (var i = 0; i < reply.length; i += 2) { 12 | obj[reply[i].toString('binary')] = reply[i + 1]; 13 | } 14 | return obj; 15 | } 16 | 17 | function replyToStrings (reply) { 18 | if (reply instanceof Buffer) { 19 | return reply.toString(); 20 | } 21 | if (reply instanceof Array) { 22 | var res = new Array(reply.length); 23 | for (var i = 0; i < reply.length; i++) { 24 | // Recusivly call the function as slowlog returns deep nested replies 25 | res[i] = replyToStrings(reply[i]); 26 | } 27 | return res; 28 | } 29 | 30 | return reply; 31 | } 32 | 33 | function print (err, reply) { 34 | if (err) { 35 | // A error always begins with Error: 36 | console.log(err.toString()); 37 | } else { 38 | console.log('Reply: ' + reply); 39 | } 40 | } 41 | 42 | var camelCase; 43 | // Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error) 44 | // Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions) 45 | // All capital letters are going to be replaced with a lower case letter and a underscore infront of it 46 | function clone (obj) { 47 | var copy; 48 | if (Array.isArray(obj)) { 49 | copy = new Array(obj.length); 50 | for (var i = 0; i < obj.length; i++) { 51 | copy[i] = clone(obj[i]); 52 | } 53 | return copy; 54 | } 55 | if (Object.prototype.toString.call(obj) === '[object Object]') { 56 | copy = {}; 57 | var elems = Object.keys(obj); 58 | var elem; 59 | while (elem = elems.pop()) { 60 | if (elem === 'tls') { // special handle tls 61 | copy[elem] = obj[elem]; 62 | continue; 63 | } 64 | // Accept camelCase options and convert them to snake_case 65 | var snake_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase(); 66 | // If camelCase is detected, pass it to the client, so all variables are going to be camelCased 67 | // There are no deep nested options objects yet, but let's handle this future proof 68 | if (snake_case !== elem.toLowerCase()) { 69 | camelCase = true; 70 | } 71 | copy[snake_case] = clone(obj[elem]); 72 | } 73 | return copy; 74 | } 75 | return obj; 76 | } 77 | 78 | function convenienceClone (obj) { 79 | camelCase = false; 80 | obj = clone(obj) || {}; 81 | if (camelCase) { 82 | obj.camel_case = true; 83 | } 84 | return obj; 85 | } 86 | 87 | function callbackOrEmit (self, callback, err, res) { 88 | if (callback) { 89 | callback(err, res); 90 | } else if (err) { 91 | self.emit('error', err); 92 | } 93 | } 94 | 95 | function replyInOrder (self, callback, err, res, queue) { 96 | // If the queue is explicitly passed, use that, otherwise fall back to the offline queue first, 97 | // as there might be commands in both queues at the same time 98 | var command_obj; 99 | /* istanbul ignore if: TODO: Remove this as soon as we test Redis 3.2 on travis */ 100 | if (queue) { 101 | command_obj = queue.peekBack(); 102 | } else { 103 | command_obj = self.offline_queue.peekBack() || self.command_queue.peekBack(); 104 | } 105 | if (!command_obj) { 106 | process.nextTick(function () { 107 | callbackOrEmit(self, callback, err, res); 108 | }); 109 | } else { 110 | var tmp = command_obj.callback; 111 | command_obj.callback = tmp ? 112 | function (e, r) { 113 | tmp(e, r); 114 | callbackOrEmit(self, callback, err, res); 115 | } : 116 | function (e, r) { 117 | if (e) { 118 | self.emit('error', e); 119 | } 120 | callbackOrEmit(self, callback, err, res); 121 | }; 122 | } 123 | } 124 | 125 | module.exports = { 126 | reply_to_strings: replyToStrings, 127 | reply_to_object: replyToObject, 128 | print: print, 129 | err_code: /^([A-Z]+)\s+(.+)$/, 130 | monitor_regex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+\]( ".+?")+$/, 131 | clone: convenienceClone, 132 | callback_or_emit: callbackOrEmit, 133 | reply_in_order: replyInOrder 134 | }; 135 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var commands = require('redis-commands'); 4 | var Multi = require('./multi'); 5 | var RedisClient = require('../').RedisClient; 6 | var Command = require('./command'); 7 | // Feature detect if a function may change it's name 8 | var changeFunctionName = (function () { 9 | var fn = function abc () {}; 10 | try { 11 | Object.defineProperty(fn, 'name', { 12 | value: 'foobar' 13 | }); 14 | return true; 15 | } catch (e) { 16 | return false; 17 | } 18 | }()); 19 | 20 | var addCommand = function (command) { 21 | // Some rare Redis commands use special characters in their command name 22 | // Convert those to a underscore to prevent using invalid function names 23 | var commandName = command.replace(/(?:^([0-9])|[^a-zA-Z0-9_$])/g, '_$1'); 24 | 25 | // Do not override existing functions 26 | if (!RedisClient.prototype[command]) { 27 | RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () { 28 | var arr; 29 | var len = arguments.length; 30 | var callback; 31 | var i = 0; 32 | if (Array.isArray(arguments[0])) { 33 | arr = arguments[0]; 34 | if (len === 2) { 35 | callback = arguments[1]; 36 | } 37 | } else if (len > 1 && Array.isArray(arguments[1])) { 38 | if (len === 3) { 39 | callback = arguments[2]; 40 | } 41 | len = arguments[1].length; 42 | arr = new Array(len + 1); 43 | arr[0] = arguments[0]; 44 | for (; i < len; i += 1) { 45 | arr[i + 1] = arguments[1][i]; 46 | } 47 | } else { 48 | // The later should not be the average use case 49 | if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { 50 | len--; 51 | callback = arguments[len]; 52 | } 53 | arr = new Array(len); 54 | for (; i < len; i += 1) { 55 | arr[i] = arguments[i]; 56 | } 57 | } 58 | return this.internal_send_command(new Command(command, arr, callback)); 59 | }; 60 | // Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run) 61 | if (commandName !== command) { 62 | RedisClient.prototype[commandName.toUpperCase()] = RedisClient.prototype[commandName] = RedisClient.prototype[command]; 63 | } 64 | if (changeFunctionName) { 65 | Object.defineProperty(RedisClient.prototype[command], 'name', { 66 | value: commandName 67 | }); 68 | } 69 | } 70 | 71 | // Do not override existing functions 72 | if (!Multi.prototype[command]) { 73 | Multi.prototype[command.toUpperCase()] = Multi.prototype[command] = function () { 74 | var arr; 75 | var len = arguments.length; 76 | var callback; 77 | var i = 0; 78 | if (Array.isArray(arguments[0])) { 79 | arr = arguments[0]; 80 | if (len === 2) { 81 | callback = arguments[1]; 82 | } 83 | } else if (len > 1 && Array.isArray(arguments[1])) { 84 | if (len === 3) { 85 | callback = arguments[2]; 86 | } 87 | len = arguments[1].length; 88 | arr = new Array(len + 1); 89 | arr[0] = arguments[0]; 90 | for (; i < len; i += 1) { 91 | arr[i + 1] = arguments[1][i]; 92 | } 93 | } else { 94 | // The later should not be the average use case 95 | if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) { 96 | len--; 97 | callback = arguments[len]; 98 | } 99 | arr = new Array(len); 100 | for (; i < len; i += 1) { 101 | arr[i] = arguments[i]; 102 | } 103 | } 104 | this.queue.push(new Command(command, arr, callback)); 105 | return this; 106 | }; 107 | // Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run) 108 | if (commandName !== command) { 109 | Multi.prototype[commandName.toUpperCase()] = Multi.prototype[commandName] = Multi.prototype[command]; 110 | } 111 | if (changeFunctionName) { 112 | Object.defineProperty(Multi.prototype[command], 'name', { 113 | value: commandName 114 | }); 115 | } 116 | } 117 | }; 118 | 119 | commands.list.forEach(addCommand); 120 | 121 | module.exports = addCommand; 122 | -------------------------------------------------------------------------------- /test/prefix.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('./lib/config'); 5 | var helper = require('./helper'); 6 | var redis = config.redis; 7 | 8 | describe('prefix key names', function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client = null; 14 | 15 | beforeEach(function (done) { 16 | client = redis.createClient({ 17 | parser: parser, 18 | prefix: 'test:prefix:' 19 | }); 20 | client.on('ready', function () { 21 | client.flushdb(function (err) { 22 | done(err); 23 | }); 24 | }); 25 | }); 26 | 27 | afterEach(function () { 28 | client.end(true); 29 | }); 30 | 31 | it('auto prefix set / get', function (done) { 32 | client.set('key', 'value', function (err, reply) { 33 | assert.strictEqual(reply, 'OK'); 34 | }); 35 | client.get('key', function (err, reply) { 36 | assert.strictEqual(reply, 'value'); 37 | }); 38 | client.getrange('key', 1, -1, function (err, reply) { 39 | assert.strictEqual(reply, 'alue'); 40 | assert.strictEqual(err, null); 41 | }); 42 | client.exists('key', function (err, res) { 43 | assert.strictEqual(res, 1); 44 | }); 45 | client.exists('test:prefix:key', function (err, res) { 46 | // The key will be prefixed itself 47 | assert.strictEqual(res, 0); 48 | }); 49 | client.mset('key2', 'value2', 'key3', 'value3'); 50 | client.keys('*', function (err, res) { 51 | assert.strictEqual(res.length, 3); 52 | assert(res.indexOf('test:prefix:key') !== -1); 53 | assert(res.indexOf('test:prefix:key2') !== -1); 54 | assert(res.indexOf('test:prefix:key3') !== -1); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('auto prefix set / get with .batch', function (done) { 60 | var batch = client.batch(); 61 | batch.set('key', 'value', function (err, reply) { 62 | assert.strictEqual(reply, 'OK'); 63 | }); 64 | batch.get('key', function (err, reply) { 65 | assert.strictEqual(reply, 'value'); 66 | }); 67 | batch.getrange('key', 1, -1, function (err, reply) { 68 | assert.strictEqual(reply, 'alue'); 69 | assert.strictEqual(err, null); 70 | }); 71 | batch.exists('key', function (err, res) { 72 | assert.strictEqual(res, 1); 73 | }); 74 | batch.exists('test:prefix:key', function (err, res) { 75 | // The key will be prefixed itself 76 | assert.strictEqual(res, 0); 77 | }); 78 | batch.mset('key2', 'value2', 'key3', 'value3'); 79 | batch.keys('*', function (err, res) { 80 | assert.strictEqual(res.length, 3); 81 | assert(res.indexOf('test:prefix:key') !== -1); 82 | assert(res.indexOf('test:prefix:key2') !== -1); 83 | assert(res.indexOf('test:prefix:key3') !== -1); 84 | }); 85 | batch.exec(done); 86 | }); 87 | 88 | it('auto prefix set / get with .multi', function (done) { 89 | var multi = client.multi(); 90 | multi.set('key', 'value', function (err, reply) { 91 | assert.strictEqual(reply, 'OK'); 92 | }); 93 | multi.get('key', function (err, reply) { 94 | assert.strictEqual(reply, 'value'); 95 | }); 96 | multi.getrange('key', 1, -1, function (err, reply) { 97 | assert.strictEqual(reply, 'alue'); 98 | assert.strictEqual(err, null); 99 | }); 100 | multi.exists('key', function (err, res) { 101 | assert.strictEqual(res, 1); 102 | }); 103 | multi.exists('test:prefix:key', function (err, res) { 104 | // The key will be prefixed itself 105 | assert.strictEqual(res, 0); 106 | }); 107 | multi.mset('key2', 'value2', 'key3', 'value3'); 108 | multi.keys('*', function (err, res) { 109 | assert.strictEqual(res.length, 3); 110 | assert(res.indexOf('test:prefix:key') !== -1); 111 | assert(res.indexOf('test:prefix:key2') !== -1); 112 | assert(res.indexOf('test:prefix:key3') !== -1); 113 | }); 114 | multi.exec(done); 115 | }); 116 | 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/commands/hmset.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'hmset' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | var client; 14 | var hash = 'test hash'; 15 | 16 | beforeEach(function (done) { 17 | client = redis.createClient.apply(null, args); 18 | client.once('ready', function () { 19 | client.flushdb(done); 20 | }); 21 | }); 22 | 23 | it('handles redis-style syntax', function (done) { 24 | client.HMSET(hash, '0123456789', 'abcdefghij', 'some manner of key', 'a type of value', 'otherTypes', 555, helper.isString('OK')); 25 | client.HGETALL(hash, function (err, obj) { 26 | assert.equal(obj['0123456789'], 'abcdefghij'); 27 | assert.equal(obj['some manner of key'], 'a type of value'); 28 | return done(err); 29 | }); 30 | }); 31 | 32 | it('handles object-style syntax', function (done) { 33 | client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, helper.isString('OK')); 34 | client.HGETALL(hash, function (err, obj) { 35 | assert.equal(obj['0123456789'], 'abcdefghij'); 36 | assert.equal(obj['some manner of key'], 'a type of value'); 37 | return done(err); 38 | }); 39 | }); 40 | 41 | it('handles object-style syntax and the key being a number', function (done) { 42 | client.HMSET(231232, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, undefined); 43 | client.HGETALL(231232, function (err, obj) { 44 | assert.equal(obj['0123456789'], 'abcdefghij'); 45 | assert.equal(obj['some manner of key'], 'a type of value'); 46 | return done(err); 47 | }); 48 | }); 49 | 50 | it('allows a numeric key', function (done) { 51 | client.HMSET(hash, 99, 'banana', helper.isString('OK')); 52 | client.HGETALL(hash, function (err, obj) { 53 | assert.equal(obj['99'], 'banana'); 54 | return done(err); 55 | }); 56 | }); 57 | 58 | it('allows a numeric key without callback', function (done) { 59 | client.HMSET(hash, 99, 'banana', 'test', 25); 60 | client.HGETALL(hash, function (err, obj) { 61 | assert.equal(obj['99'], 'banana'); 62 | assert.equal(obj.test, '25'); 63 | return done(err); 64 | }); 65 | }); 66 | 67 | it('allows an array without callback', function (done) { 68 | client.HMSET([hash, 99, 'banana', 'test', 25]); 69 | client.HGETALL(hash, function (err, obj) { 70 | assert.equal(obj['99'], 'banana'); 71 | assert.equal(obj.test, '25'); 72 | return done(err); 73 | }); 74 | }); 75 | 76 | it('allows an array and a callback', function (done) { 77 | client.HMSET([hash, 99, 'banana', 'test', 25], helper.isString('OK')); 78 | client.HGETALL(hash, function (err, obj) { 79 | assert.equal(obj['99'], 'banana'); 80 | assert.equal(obj.test, '25'); 81 | return done(err); 82 | }); 83 | }); 84 | 85 | it('allows a key plus array without callback', function (done) { 86 | client.HMSET(hash, [99, 'banana', 'test', 25]); 87 | client.HGETALL(hash, function (err, obj) { 88 | assert.equal(obj['99'], 'banana'); 89 | assert.equal(obj.test, '25'); 90 | return done(err); 91 | }); 92 | }); 93 | 94 | it('allows a key plus array and a callback', function (done) { 95 | client.HMSET(hash, [99, 'banana', 'test', 25], helper.isString('OK')); 96 | client.HGETALL(hash, function (err, obj) { 97 | assert.equal(obj['99'], 'banana'); 98 | assert.equal(obj.test, '25'); 99 | return done(err); 100 | }); 101 | }); 102 | 103 | it('handles object-style syntax without callback', function (done) { 104 | client.HMSET(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'}); 105 | client.HGETALL(hash, function (err, obj) { 106 | assert.equal(obj['0123456789'], 'abcdefghij'); 107 | assert.equal(obj['some manner of key'], 'a type of value'); 108 | return done(err); 109 | }); 110 | }); 111 | 112 | afterEach(function () { 113 | client.end(true); 114 | }); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/commands/sort.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | function setupData (client, done) { 9 | client.rpush('y', 'd'); 10 | client.rpush('y', 'b'); 11 | client.rpush('y', 'a'); 12 | client.rpush('y', 'c'); 13 | 14 | client.rpush('x', '3'); 15 | client.rpush('x', '9'); 16 | client.rpush('x', '2'); 17 | client.rpush('x', '4'); 18 | 19 | client.set('w3', '4'); 20 | client.set('w9', '5'); 21 | client.set('w2', '12'); 22 | client.set('w4', '6'); 23 | 24 | client.set('o2', 'buz'); 25 | client.set('o3', 'foo'); 26 | client.set('o4', 'baz'); 27 | client.set('o9', 'bar'); 28 | 29 | client.set('p2', 'qux'); 30 | client.set('p3', 'bux'); 31 | client.set('p4', 'lux'); 32 | client.set('p9', 'tux', done); 33 | } 34 | 35 | describe("The 'sort' method", function () { 36 | 37 | helper.allTests(function (parser, ip, args) { 38 | 39 | describe('using ' + parser + ' and ' + ip, function () { 40 | var client; 41 | 42 | beforeEach(function (done) { 43 | client = redis.createClient.apply(null, args); 44 | client.once('error', done); 45 | client.once('connect', function () { 46 | client.flushdb(); 47 | setupData(client, done); 48 | }); 49 | }); 50 | 51 | describe('alphabetical', function () { 52 | it('sorts in ascending alphabetical order', function (done) { 53 | client.sort('y', 'asc', 'alpha', function (err, sorted) { 54 | assert.deepEqual(sorted, ['a', 'b', 'c', 'd']); 55 | return done(err); 56 | }); 57 | }); 58 | 59 | it('sorts in descending alphabetical order', function (done) { 60 | client.SORT('y', 'desc', 'alpha', function (err, sorted) { 61 | assert.deepEqual(sorted, ['d', 'c', 'b', 'a']); 62 | return done(err); 63 | }); 64 | }); 65 | }); 66 | 67 | describe('numeric', function () { 68 | it('sorts in ascending numeric order', function (done) { 69 | client.sort('x', 'asc', function (err, sorted) { 70 | assert.deepEqual(sorted, [2, 3, 4, 9]); 71 | return done(err); 72 | }); 73 | }); 74 | 75 | it('sorts in descending numeric order', function (done) { 76 | client.sort('x', 'desc', function (err, sorted) { 77 | assert.deepEqual(sorted, [9, 4, 3, 2]); 78 | return done(err); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('pattern', function () { 84 | it('handles sorting with a pattern', function (done) { 85 | client.sort('x', 'by', 'w*', 'asc', function (err, sorted) { 86 | assert.deepEqual(sorted, [3, 9, 4, 2]); 87 | return done(err); 88 | }); 89 | }); 90 | 91 | it("handles sorting with a 'by' pattern and 1 'get' pattern", function (done) { 92 | client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', function (err, sorted) { 93 | assert.deepEqual(sorted, ['foo', 'bar', 'baz', 'buz']); 94 | return done(err); 95 | }); 96 | }); 97 | 98 | it("handles sorting with a 'by' pattern and 2 'get' patterns", function (done) { 99 | client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', function (err, sorted) { 100 | assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']); 101 | return done(err); 102 | }); 103 | }); 104 | 105 | it("handles sorting with a 'by' pattern and 2 'get' patterns with the array syntax", function (done) { 106 | client.sort(['x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*'], function (err, sorted) { 107 | assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']); 108 | return done(err); 109 | }); 110 | }); 111 | 112 | it("sorting with a 'by' pattern and 2 'get' patterns and stores results", function (done) { 113 | client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', function (err) { 114 | if (err) return done(err); 115 | }); 116 | 117 | client.lrange('bacon', 0, -1, function (err, values) { 118 | assert.deepEqual(values, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']); 119 | return done(err); 120 | }); 121 | }); 122 | }); 123 | 124 | afterEach(function () { 125 | client.end(true); 126 | }); 127 | }); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /test/rename.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('./lib/config'); 5 | var helper = require('./helper'); 6 | var redis = config.redis; 7 | 8 | if (process.platform === 'win32') { 9 | // TODO: Fix redis process spawn on windows 10 | return; 11 | } 12 | 13 | describe('rename commands', function () { 14 | before(function (done) { 15 | helper.stopRedis(function () { 16 | helper.startRedis('./conf/rename.conf', done); 17 | }); 18 | }); 19 | 20 | helper.allTests(function (parser, ip, args) { 21 | 22 | describe('using ' + parser + ' and ' + ip, function () { 23 | var client = null; 24 | 25 | beforeEach(function (done) { 26 | if (helper.redisProcess().spawnFailed()) return done(); 27 | client = redis.createClient({ 28 | rename_commands: { 29 | set: '807081f5afa96845a02816a28b7258c3', 30 | GETRANGE: '9e3102b15cf231c4e9e940f284744fe0' 31 | }, 32 | parser: parser 33 | }); 34 | 35 | client.on('ready', function () { 36 | client.flushdb(done); 37 | }); 38 | }); 39 | 40 | afterEach(function () { 41 | if (helper.redisProcess().spawnFailed()) return; 42 | client.end(true); 43 | }); 44 | 45 | it('allows to use renamed functions', function (done) { 46 | if (helper.redisProcess().spawnFailed()) this.skip(); 47 | 48 | client.set('key', 'value', function (err, reply) { 49 | assert.strictEqual(reply, 'OK'); 50 | }); 51 | 52 | client.get('key', function (err, reply) { 53 | assert.strictEqual(err.message, "ERR unknown command 'get'"); 54 | assert.strictEqual(err.command, 'GET'); 55 | assert.strictEqual(reply, undefined); 56 | }); 57 | 58 | client.getrange('key', 1, -1, function (err, reply) { 59 | assert.strictEqual(reply, 'alue'); 60 | assert.strictEqual(err, null); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('should also work with batch', function (done) { 66 | if (helper.redisProcess().spawnFailed()) this.skip(); 67 | 68 | client.batch([['set', 'key', 'value']]).exec(function (err, res) { 69 | assert.strictEqual(res[0], 'OK'); 70 | }); 71 | 72 | var batch = client.batch(); 73 | batch.getrange('key', 1, -1); 74 | batch.exec(function (err, res) { 75 | assert(!err); 76 | assert.strictEqual(res.length, 1); 77 | assert.strictEqual(res[0], 'alue'); 78 | done(); 79 | }); 80 | }); 81 | 82 | it('should also work with multi', function (done) { 83 | if (helper.redisProcess().spawnFailed()) this.skip(); 84 | 85 | client.multi([['set', 'key', 'value']]).exec(function (err, res) { 86 | assert.strictEqual(res[0], 'OK'); 87 | }); 88 | 89 | var multi = client.multi(); 90 | multi.getrange('key', 1, -1); 91 | multi.exec(function (err, res) { 92 | assert(!err); 93 | assert.strictEqual(res.length, 1); 94 | assert.strictEqual(res[0], 'alue'); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should also work with multi and abort transaction', function (done) { 100 | if (helper.redisProcess().spawnFailed()) this.skip(); 101 | 102 | var multi = client.multi(); 103 | multi.get('key'); 104 | multi.getrange('key', 1, -1, function (err, reply) { 105 | assert.strictEqual(reply, 'alue'); 106 | assert.strictEqual(err, null); 107 | }); 108 | multi.exec(function (err, res) { 109 | assert(err); 110 | assert.strictEqual(err.message, 'EXECABORT Transaction discarded because of previous errors.'); 111 | assert.strictEqual(err.errors[0].message, "ERR unknown command 'get'"); 112 | assert.strictEqual(err.errors[0].command, 'GET'); 113 | assert.strictEqual(err.code, 'EXECABORT'); 114 | assert.strictEqual(err.errors[0].code, 'ERR'); 115 | done(); 116 | }); 117 | }); 118 | 119 | it('should also work prefixed commands', function (done) { 120 | if (helper.redisProcess().spawnFailed()) this.skip(); 121 | 122 | client.end(true); 123 | client = redis.createClient({ 124 | rename_commands: { 125 | set: '807081f5afa96845a02816a28b7258c3' 126 | }, 127 | parser: parser, 128 | prefix: 'baz' 129 | }); 130 | client.set('foo', 'bar'); 131 | client.keys('*', function (err, reply) { 132 | assert.strictEqual(reply[0], 'bazfoo'); 133 | assert.strictEqual(err, null); 134 | done(); 135 | }); 136 | }); 137 | 138 | }); 139 | }); 140 | 141 | after(function (done) { 142 | if (helper.redisProcess().spawnFailed()) return done(); 143 | helper.stopRedis(function () { 144 | helper.startRedis('./conf/redis.conf', done); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /test/commands/select.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('../lib/config'); 5 | var helper = require('../helper'); 6 | var redis = config.redis; 7 | 8 | describe("The 'select' method", function () { 9 | 10 | helper.allTests(function (parser, ip, args) { 11 | 12 | describe('using ' + parser + ' and ' + ip, function () { 13 | describe('when not connected', function () { 14 | var client; 15 | 16 | beforeEach(function (done) { 17 | client = redis.createClient.apply(null, args); 18 | client.once('ready', function () { 19 | client.quit(); 20 | }); 21 | client.on('end', done); 22 | }); 23 | 24 | it('returns an error if redis is not connected', function (done) { 25 | var buffering = client.select(1, function (err, res) { 26 | assert(err.message.match(/The connection is already closed/)); 27 | done(); 28 | }); 29 | assert(typeof buffering === 'boolean'); 30 | }); 31 | }); 32 | 33 | describe('when connected', function () { 34 | var client; 35 | 36 | beforeEach(function (done) { 37 | client = redis.createClient.apply(null, args); 38 | client.once('ready', function () { 39 | client.flushdb(done); 40 | }); 41 | }); 42 | 43 | afterEach(function () { 44 | client.end(true); 45 | }); 46 | 47 | it('changes the database and calls the callback', function (done) { 48 | // default value of null means database 0 will be used. 49 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 50 | var buffering = client.SELECT(1, function (err, res) { 51 | helper.isNotError()(err, res); 52 | assert.strictEqual(client.selected_db, 1, 'db should be 1 after select'); 53 | done(); 54 | }); 55 | assert(typeof buffering === 'boolean'); 56 | }); 57 | 58 | describe('and a callback is specified', function () { 59 | describe('with a valid db index', function () { 60 | it('selects the appropriate database', function (done) { 61 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 62 | client.select(1, function (err) { 63 | assert.equal(err, null); 64 | assert.equal(client.selected_db, 1, 'we should have selected the new valid DB'); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe('with an invalid db index', function () { 71 | it('returns an error', function (done) { 72 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 73 | client.select(9999, function (err) { 74 | assert.equal(err.code, 'ERR'); 75 | assert((err.message === 'ERR DB index is out of range' || err.message === 'ERR invalid DB index')); 76 | done(); 77 | }); 78 | }); 79 | }); 80 | }); 81 | 82 | describe('and no callback is specified', function () { 83 | describe('with a valid db index', function () { 84 | it('selects the appropriate database', function (done) { 85 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 86 | client.select(1); 87 | setTimeout(function () { 88 | assert.equal(client.selected_db, 1, 'we should have selected the new valid DB'); 89 | done(); 90 | }, 25); 91 | }); 92 | }); 93 | 94 | describe('with an invalid db index', function () { 95 | it('emits an error when callback not provided', function (done) { 96 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 97 | 98 | client.on('error', function (err) { 99 | assert.strictEqual(err.command, 'SELECT'); 100 | assert((err.message === 'ERR DB index is out of range' || err.message === 'ERR invalid DB index')); 101 | done(); 102 | }); 103 | 104 | client.select(9999); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('reconnection occurs', function () { 110 | it('selects the appropriate database after a reconnect', function (done) { 111 | assert.strictEqual(client.selected_db, undefined, 'default db should be undefined'); 112 | client.select(3); 113 | client.set('foo', 'bar', function () { 114 | client.stream.destroy(); 115 | }); 116 | client.once('ready', function () { 117 | assert.strictEqual(client.selected_db, 3); 118 | assert(typeof client.server_info.db3 === 'object'); 119 | done(); 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/tls.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var config = require('./lib/config'); 5 | var fs = require('fs'); 6 | var helper = require('./helper'); 7 | var path = require('path'); 8 | var redis = config.redis; 9 | var utils = require('../lib/utils'); 10 | var tls = require('tls'); 11 | 12 | var tls_options = { 13 | servername: 'redis.js.org', 14 | rejectUnauthorized: true, 15 | ca: [ String(fs.readFileSync(path.resolve(__dirname, './conf/redis.js.org.cert'))) ] 16 | }; 17 | 18 | var tls_port = 6380; 19 | // Use skip instead of returning to indicate what tests really got skipped 20 | var skip = false; 21 | 22 | // Wait until stunnel4 is in the travis whitelist 23 | // Check: https://github.com/travis-ci/apt-package-whitelist/issues/403 24 | // If this is merged, remove the travis env checks 25 | describe('TLS connection tests', function () { 26 | 27 | before(function (done) { 28 | // Print the warning when the tests run instead of while starting mocha 29 | if (process.platform === 'win32') { 30 | skip = true; 31 | console.warn('\nStunnel tests do not work on windows atm. If you think you can fix that, it would be warmly welcome.\n'); 32 | } else if (process.env.TRAVIS === 'true') { 33 | skip = true; 34 | console.warn('\nTravis does not support stunnel right now. Skipping tests.\nCheck: https://github.com/travis-ci/apt-package-whitelist/issues/403\n'); 35 | } 36 | if (skip) return done(); 37 | helper.stopStunnel(function () { 38 | helper.startStunnel(done); 39 | }); 40 | }); 41 | 42 | after(function (done) { 43 | if (skip) return done(); 44 | helper.stopStunnel(done); 45 | }); 46 | 47 | var client; 48 | 49 | afterEach(function () { 50 | if (skip) return; 51 | client.end(true); 52 | }); 53 | 54 | describe('on lost connection', function () { 55 | it('emit an error after max retry timeout and do not try to reconnect afterwards', function (done) { 56 | if (skip) this.skip(); 57 | var connect_timeout = 500; // in ms 58 | client = redis.createClient({ 59 | connect_timeout: connect_timeout, 60 | port: tls_port, 61 | tls: tls_options 62 | }); 63 | var time = 0; 64 | assert.strictEqual(client.address, '127.0.0.1:' + tls_port); 65 | 66 | client.once('ready', function () { 67 | helper.killConnection(client); 68 | }); 69 | 70 | client.on('reconnecting', function (params) { 71 | time += params.delay; 72 | }); 73 | 74 | client.on('error', function (err) { 75 | if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) { 76 | process.nextTick(function () { 77 | assert.strictEqual(time, connect_timeout); 78 | assert.strictEqual(client.emitted_end, true); 79 | assert.strictEqual(client.connected, false); 80 | assert.strictEqual(client.ready, false); 81 | assert.strictEqual(client.closing, true); 82 | assert.strictEqual(time, connect_timeout); 83 | done(); 84 | }); 85 | } 86 | }); 87 | }); 88 | }); 89 | 90 | describe('when not connected', function () { 91 | 92 | it('connect with host and port provided in the tls object', function (done) { 93 | if (skip) this.skip(); 94 | var tls_opts = utils.clone(tls_options); 95 | tls_opts.port = tls_port; 96 | tls_opts.host = 'localhost'; 97 | client = redis.createClient({ 98 | connect_timeout: 1000, 99 | tls: tls_opts 100 | }); 101 | 102 | // verify connection is using TCP, not UNIX socket 103 | assert.strictEqual(client.connection_options.host, 'localhost'); 104 | assert.strictEqual(client.connection_options.port, tls_port); 105 | assert.strictEqual(client.address, 'localhost:' + tls_port); 106 | assert(client.stream.encrypted); 107 | 108 | client.set('foo', 'bar'); 109 | client.get('foo', helper.isString('bar', done)); 110 | }); 111 | 112 | describe('using rediss as url protocol', function (done) { 113 | var tls_connect = tls.connect; 114 | beforeEach(function () { 115 | tls.connect = function (options) { 116 | options = utils.clone(options); 117 | options.ca = tls_options.ca; 118 | return tls_connect.call(tls, options); 119 | }; 120 | }); 121 | afterEach(function () { 122 | tls.connect = tls_connect; 123 | }); 124 | it('connect with tls when rediss is used as the protocol', function (done) { 125 | if (skip) this.skip(); 126 | client = redis.createClient('rediss://localhost:' + tls_port); 127 | // verify connection is using TCP, not UNIX socket 128 | assert(client.stream.encrypted); 129 | client.set('foo', 'bar'); 130 | client.get('foo', helper.isString('bar', done)); 131 | }); 132 | }); 133 | 134 | it('fails to connect because the cert is not correct', function (done) { 135 | if (skip) this.skip(); 136 | var faulty_cert = utils.clone(tls_options); 137 | faulty_cert.ca = [ String(fs.readFileSync(path.resolve(__dirname, './conf/faulty.cert'))) ]; 138 | client = redis.createClient({ 139 | host: 'localhost', 140 | connect_timeout: 1000, 141 | port: tls_port, 142 | tls: faulty_cert 143 | }); 144 | assert.strictEqual(client.address, 'localhost:' + tls_port); 145 | client.on('error', function (err) { 146 | assert(/DEPTH_ZERO_SELF_SIGNED_CERT/.test(err.code || err.message), err); 147 | client.end(true); 148 | }); 149 | client.set('foo', 'bar', function (err, res) { 150 | done(res); 151 | }); 152 | }); 153 | 154 | }); 155 | }); 156 | --------------------------------------------------------------------------------