diff --git a/index.js b/index.js index 5b493ea621..cfb123ee6c 100644 --- a/index.js +++ b/index.js @@ -308,7 +308,6 @@ RedisClient.prototype.on_ready = function () { if (this.old_state !== null) { this.monitoring = this.old_state.monitoring; this.pub_sub_mode = this.old_state.pub_sub_mode; - this.selected_db = this.old_state.selected_db; this.old_state = null; } @@ -456,13 +455,11 @@ RedisClient.prototype.connection_gone = function (why) { if (this.old_state === null) { var state = { monitoring: this.monitoring, - pub_sub_mode: this.pub_sub_mode, - selected_db: this.selected_db + pub_sub_mode: this.pub_sub_mode }; this.old_state = state; this.monitoring = false; this.pub_sub_mode = false; - this.selected_db = undefined; } // since we are collapsing end and close, users don't expect to be called twice @@ -940,7 +937,7 @@ RedisClient.prototype.select = RedisClient.prototype.SELECT = function (db, call }); }; -// Store db in this.select_db to restore it on reconnect +// Store info in this.server_info after each call RedisClient.prototype.info = RedisClient.prototype.INFO = function (callback) { var self = this; this.send_anyway = true; diff --git a/test/auth.spec.js b/test/auth.spec.js index 1e79f023e1..9e648d63e0 100644 --- a/test/auth.spec.js +++ b/test/auth.spec.js @@ -67,12 +67,38 @@ describe("client authentication", function () { }); if (ip === 'IPv4') { - it('allows auth to be provided as part of redis url', function (done) { + it('allows auth to be provided as part of redis url and do not fire commands before auth is done', function (done) { if (helper.redisProcess().spawnFailed()) this.skip(); - client = redis.createClient('redis://foo:' + auth + '@' + config.HOST[ip] + ':' + config.PORT); + var end = helper.callFuncAfter(done, 2); + client = redis.createClient('redis://:' + auth + '@' + config.HOST[ip] + ':' + config.PORT); client.on("ready", function () { - return done(); + end(); + }); + // The info command may be used while loading but not if not yet authenticated + client.info(function (err, res) { + assert(!err); + end(); + }); + }); + + it('allows auth and database to be provided as part of redis url query parameter', function (done) { + if (helper.redisProcess().spawnFailed()) this.skip(); + + client = redis.createClient('redis://' + config.HOST[ip] + ':' + config.PORT + '?db=2&password=' + auth); + assert.strictEqual(client.options.db, '2'); + assert.strictEqual(client.options.password, auth); + assert.strictEqual(client.auth_pass, auth); + client.on("ready", function () { + // Set a key so the used database is returned in the info command + client.set('foo', 'bar'); + client.get('foo'); + assert.strictEqual(client.server_info.db2, undefined); + // Using the info command should update the server_info + client.info(function (err, res) { + assert(typeof client.server_info.db2 === 'object'); + }); + client.flushdb(done); }); }); } @@ -93,7 +119,7 @@ describe("client authentication", function () { if (helper.redisProcess().spawnFailed()) this.skip(); var args = config.configureClient(parser, ip, { - auth_pass: auth, + password: auth, no_ready_check: true }); client = redis.createClient.apply(redis.createClient, args); @@ -195,6 +221,18 @@ describe("client authentication", function () { done(); }); }); + + it('should emit an error if the provided password is faulty', function (done) { + if (helper.redisProcess().spawnFailed()) this.skip(); + client = redis.createClient({ + password: 'wrong_password', + parser: parser + }); + client.once("error", function (err) { + assert.strictEqual(err.message, 'ERR invalid password'); + done(); + }); + }); }); }); diff --git a/test/batch.spec.js b/test/batch.spec.js index caf4e7b8ef..1c8b51bcc1 100644 --- a/test/batch.spec.js +++ b/test/batch.spec.js @@ -179,7 +179,7 @@ describe("The 'batch' method", function () { client.BATCH([ ["smembers", ["some set"]], ["del", "some set"], - ["smembers", "some set"] + ["smembers", "some set", undefined] // The explicit undefined is handled as a callback that is undefined ]) .scard("some set") .exec(function (err, replies) { diff --git a/test/commands/hset.spec.js b/test/commands/hset.spec.js index c08150382e..2d0932476a 100644 --- a/test/commands/hset.spec.js +++ b/test/commands/hset.spec.js @@ -45,12 +45,40 @@ describe("The 'hset' method", function () { }); }); - it('does not error when a buffer and array are set as fields on the same hash', function (done) { + it('throws a error if someone passed a array either as field or as value', function (done) { + var hash = "test hash"; + var field = "array"; + // This would be converted to "array contents" but if you use more than one entry, + // it'll result in e.g. "array contents,second content" and this is not supported and considered harmful + var value = ["array contents"]; + try { + client.HMSET(hash, field, value); + throw new Error('test failed'); + } catch (err) { + if (/invalid data/.test(err.message)) { + done(); + } else { + done(err); + } + } + }); + + it('does not error when a buffer and date are set as values on the same hash', function (done) { var hash = "test hash"; var field1 = "buffer"; var value1 = new Buffer("abcdefghij"); - var field2 = "array"; - var value2 = ["array contents"]; + var field2 = "date"; + var value2 = new Date(); + + client.HMSET(hash, field1, value1, field2, value2, helper.isString("OK", done)); + }); + + it('does not error when a buffer and date are set as fields on the same hash', function (done) { + var hash = "test hash"; + var value1 = "buffer"; + var field1 = new Buffer("abcdefghij"); + var value2 = "date"; + var field2 = new Date(); client.HMSET(hash, field1, value1, field2, value2, helper.isString("OK", done)); }); diff --git a/test/commands/info.spec.js b/test/commands/info.spec.js new file mode 100644 index 0000000000..9ce968051f --- /dev/null +++ b/test/commands/info.spec.js @@ -0,0 +1,51 @@ +'use strict'; + +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; + +describe("The 'info' method", function () { + + helper.allTests(function(parser, ip, args) { + + describe("using " + parser + " and " + ip, function () { + var client; + + before(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("ready", function () { + client.flushall(done); + }); + }); + + after(function () { + client.end(true); + }); + + it("update server_info after a info command", function (done) { + client.set('foo', 'bar'); + client.info(); + client.select(2, function () { + assert.strictEqual(client.server_info.db2, undefined); + }); + client.set('foo', 'bar'); + client.info(); + setTimeout(function () { + assert.strictEqual(typeof client.server_info.db2, 'object'); + done(); + }, 150); + }); + + it("emit error after a failure", function (done) { + client.info(); + client.once('error', function (err) { + assert.strictEqual(err.code, 'UNCERTAIN_STATE'); + assert.strictEqual(err.command, 'INFO'); + done(); + }); + client.stream.destroy(); + }); + }); + }); +}); diff --git a/test/commands/keys.spec.js b/test/commands/keys.spec.js index 2c977f664d..5522c2dfaa 100644 --- a/test/commands/keys.spec.js +++ b/test/commands/keys.spec.js @@ -14,10 +14,9 @@ describe("The 'keys' method", function () { var client; beforeEach(function (done) { - args = args || {}; client = redis.createClient.apply(redis.createClient, args); client.once("ready", function () { - client.flushdb(done); + client.flushall(done); }); }); diff --git a/test/commands/select.spec.js b/test/commands/select.spec.js index bf41fc46a0..b3ef6d5893 100644 --- a/test/commands/select.spec.js +++ b/test/commands/select.spec.js @@ -37,7 +37,9 @@ describe("The 'select' method", function () { beforeEach(function (done) { client = redis.createClient.apply(redis.createClient, args); - client.once("ready", function () { done(); }); + client.once("ready", function () { + client.flushdb(done); + }); }); afterEach(function () { @@ -46,7 +48,7 @@ describe("The 'select' method", function () { it("changes the database and calls the callback", function (done) { // default value of null means database 0 will be used. - assert.strictEqual(client.selected_db, null, "default db should be null"); + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); var buffering = client.SELECT(1, function (err, res) { helper.isNotError()(err, res); assert.strictEqual(client.selected_db, 1, "db should be 1 after select"); @@ -58,7 +60,7 @@ describe("The 'select' method", function () { describe("and a callback is specified", function () { describe("with a valid db index", function () { it("selects the appropriate database", function (done) { - assert.strictEqual(client.selected_db, null, "default db should be null"); + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); client.select(1, function (err) { assert.equal(err, null); assert.equal(client.selected_db, 1, "we should have selected the new valid DB"); @@ -69,7 +71,7 @@ describe("The 'select' method", function () { describe("with an invalid db index", function () { it("returns an error", function (done) { - assert.strictEqual(client.selected_db, null, "default db should be null"); + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); client.select(9999, function (err) { assert.equal(err.code, 'ERR'); assert.equal(err.message, 'ERR invalid DB index'); @@ -82,7 +84,7 @@ describe("The 'select' method", function () { describe("and no callback is specified", function () { describe("with a valid db index", function () { it("selects the appropriate database", function (done) { - assert.strictEqual(client.selected_db, null, "default db should be null"); + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); client.select(1); setTimeout(function () { assert.equal(client.selected_db, 1, "we should have selected the new valid DB"); @@ -93,7 +95,7 @@ describe("The 'select' method", function () { describe("with an invalid db index", function () { it("emits an error when callback not provided", function (done) { - assert.strictEqual(client.selected_db, null, "default db should be null"); + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); client.on('error', function (err) { assert.strictEqual(err.command, 'SELECT'); @@ -105,6 +107,21 @@ describe("The 'select' method", function () { }); }); }); + + describe("reconnection occurs", function () { + it("selects the appropriate database after a reconnect", function (done) { + assert.strictEqual(client.selected_db, undefined, "default db should be undefined"); + client.select(3); + client.set('foo', 'bar', function () { + client.stream.destroy(); + }); + client.once('ready', function () { + assert.strictEqual(client.selected_db, 3); + assert(typeof client.server_info.db3 === 'object'); + done(); + }); + }); + }); }); }); }); diff --git a/test/commands/set.spec.js b/test/commands/set.spec.js index 4868ed06e6..c486629733 100644 --- a/test/commands/set.spec.js +++ b/test/commands/set.spec.js @@ -81,32 +81,17 @@ describe("The 'set' method", function () { describe("with valid parameters", function () { it("sets the value correctly", function (done) { client.set(key, value); - setTimeout(function () { - client.get(key, function (err, res) { - helper.isString(value)(err, res); - done(); - }); - }, 100); + client.get(key, helper.isString(value, done)); }); it("sets the value correctly even if the callback is explicitly set to undefined", function (done) { client.set(key, value, undefined); - setTimeout(function () { - client.get(key, function (err, res) { - helper.isString(value)(err, res); - done(); - }); - }, 100); + client.get(key, helper.isString(value, done)); }); it("sets the value correctly with the array syntax", function (done) { client.set([key, value]); - setTimeout(function () { - client.get(key, function (err, res) { - helper.isString(value)(err, res); - done(); - }); - }, 100); + client.get(key, helper.isString(value, done)); }); }); @@ -121,6 +106,12 @@ describe("The 'set' method", function () { }); }); + // TODO: This test has to be refactored from v.3.0 on to expect an error instead + it("converts null to 'null'", function (done) { + client.set('foo', null); + client.get('foo', helper.isString('null', done)); + }); + it("emit an error with only the key set", function (done) { client.on('error', function (err) { assert.equal(err.message, "ERR wrong number of arguments for 'set' command"); diff --git a/test/commands/sync.spec.js b/test/commands/sync.spec.js deleted file mode 100644 index bd793a0d38..0000000000 --- a/test/commands/sync.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var config = require("../lib/config"); -var helper = require("../helper"); -var redis = config.redis; - -describe.skip("The 'sync' method", function () { - - helper.allTests(function(parser, ip, args) { - - describe("using " + parser + " and " + ip, function () { - var client; - - beforeEach(function (done) { - client = redis.createClient.apply(redis.createClient, args); - client.once("ready", function () { - client.flushdb(done); - }); - }); - - // This produces a parser error - // "Protocol error, got "K" as reply type byte" - // I'm uncertain if this is correct behavior or not - // TODO: Fix the command queue state error occuring - it('try to sync with the server and fail other commands', function (done) { - client.on('error', function(err) { - assert.equal(err.message, 'Protocol error, got "K" as reply type byte'); - assert.equal(err.command, 'SET'); - done(); - }); - client.sync(function(err, res) { - assert.equal(err, null); - assert(!!res); - }); - client.set('foo', 'bar'); - }); - - afterEach(function () { - client.end(true); - }); - }); - }); -}); diff --git a/test/connection.spec.js b/test/connection.spec.js index 9e98c3732e..d731fee5fd 100644 --- a/test/connection.spec.js +++ b/test/connection.spec.js @@ -152,6 +152,7 @@ describe("connection tests", function () { client.on('error', function(err) { assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)); assert(Date.now() - time < connect_timeout + 50); + assert(Date.now() - time >= connect_timeout); done(); }); }); @@ -358,20 +359,20 @@ describe("connection tests", function () { redis.createClient(config.HOST[ip] + ':' + config.PORT); throw new Error('failed'); } catch (err) { - assert.equal(err.message, 'Connection string must use the "redis:" protocol'); + assert.equal(err.message, 'Connection string must use the "redis:" protocol or begin with slashes //'); } }); if (ip === 'IPv4') { it('allows connecting with the redis url and the default port', function (done) { - client = redis.createClient('redis://foo:porkchopsandwiches@' + config.HOST[ip]); + client = redis.createClient('redis://:porkchopsandwiches@' + config.HOST[ip] + '/'); client.on("ready", function () { return done(); }); }); it('allows connecting with the redis url as first parameter and the options as second parameter', function (done) { - client = redis.createClient('redis://127.0.0.1', { + client = redis.createClient('//127.0.0.1', { connect_timeout: 1000 }); assert.strictEqual(client.options.connect_timeout, 1000); @@ -380,11 +381,12 @@ describe("connection tests", function () { }); }); - it('allows connecting with the redis url in the options object', function (done) { + it('allows connecting with the redis url in the options object and works with protocols other than the redis protocol (e.g. http)', function (done) { client = redis.createClient({ - url: 'redis://foo:porkchopsandwiches@' + config.HOST[ip] + url: 'http://foo:porkchopsandwiches@' + config.HOST[ip] + '/3' }); - assert.strictEqual(client.options.auth_pass, 'porkchopsandwiches'); + assert.strictEqual(client.auth_pass, 'porkchopsandwiches'); + assert.strictEqual(+client.selected_db, 3); assert(!client.options.port); assert.strictEqual(client.options.host, config.HOST[ip]); client.on("ready", function () { @@ -424,7 +426,8 @@ describe("connection tests", function () { tmp(function(err, res) { if (!delayed) { assert(!err); - res = res.toString().replace(/loading:0/, 'loading:1\r\nloading_eta_seconds:0.5'); + client.server_info.loading = 1; + client.server_info.loading_eta_seconds = 0.5; delayed = true; time = Date.now(); } @@ -454,7 +457,8 @@ describe("connection tests", function () { if (!delayed) { assert(!err); // Try reconnecting after one second even if redis tells us the time needed is above one second - res = res.toString().replace(/loading:0/, 'loading:1\r\nloading_eta_seconds:2.5'); + client.server_info.loading = 1; + client.server_info.loading_eta_seconds = 2.5; delayed = true; time = Date.now(); } diff --git a/test/multi.spec.js b/test/multi.spec.js index 63541e8a50..86dfe93162 100644 --- a/test/multi.spec.js +++ b/test/multi.spec.js @@ -335,7 +335,7 @@ describe("The 'multi' method", function () { ["hmset", arr3, helper.isString('OK')], ['hmset', now, {123456789: "abcdefghij", "some manner of key": "a type of value", "otherTypes": 555}], ['hmset', 'key2', {"0123456789": "abcdefghij", "some manner of key": "a type of value", "otherTypes": 999}, helper.isString('OK')], - ["HMSET", "multihmset", ["multibar", "multibaz"]], + ["HMSET", "multihmset", ["multibar", "multibaz"], undefined], // undefined is used as a explicit not set callback variable ["hmset", "multihmset", ["multibar", "multibaz"], helper.isString('OK')], ]) .hmget(now, 123456789, 'otherTypes') diff --git a/test/node_redis.spec.js b/test/node_redis.spec.js index 7b3171f8d5..e4ef75f00b 100644 --- a/test/node_redis.spec.js +++ b/test/node_redis.spec.js @@ -8,9 +8,7 @@ var redis = config.redis; describe("The node_redis client", function () { - helper.allTests({ - allConnections: true - }, function(parser, ip, args) { + helper.allTests(function(parser, ip, args) { describe("using " + parser + " and " + ip, function () { var client; @@ -128,7 +126,7 @@ describe("The node_redis client", function () { it("misusing the function should eventually throw (no command)", function (done) { client.send_command(true, 'info', function (err, res) { assert(/ERR Protocol error/.test(err.message)); - assert.equal(err.command, true); + assert.equal(err.command, undefined); assert.equal(err.code, 'ERR'); done(); }); @@ -143,6 +141,28 @@ describe("The node_redis client", function () { }); + describe("retry_unfulfilled_commands", function () { + + it("should retry all commands instead of returning an error if a command did not yet return after a connection loss", function (done) { + var bclient = redis.createClient({ + parser: parser, + retry_unfulfilled_commands: true + }); + bclient.blpop("blocking list 2", 5, function (err, value) { + assert.strictEqual(value[0], "blocking list 2"); + assert.strictEqual(value[1], "initial value"); + return done(err); + }); + bclient.once('ready', function () { + setTimeout(function () { + bclient.stream.destroy(); + client.rpush("blocking list 2", "initial value", helper.isNumber(1)); + }, 100); + }); + }); + + }); + describe(".end", function () { it('used without flush / flush set to false', function(done) { @@ -191,8 +211,8 @@ describe("The node_redis client", function () { it("return an error in the callback", function (done) { if (helper.redisProcess().spawnFailed()) this.skip(); - // TODO: Investigate why this test is failing hard and killing mocha if the client is created with .apply - // Seems like something is wrong while passing a socket connection to create client! args[1] + // TODO: Investigate why this test is failing hard and killing mocha. + // Seems like something is wrong with nyc while passing a socket connection to create client! client = redis.createClient(); client.quit(function() { client.get("foo", function(err, res) {