1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00
Files
node-redis/test/node_redis.spec.js

683 lines
32 KiB
JavaScript

'use strict';
var assert = require("assert");
var config = require("./lib/config");
var helper = require('./helper');
var fork = require("child_process").fork;
var redis = config.redis;
describe("The node_redis client", function () {
describe("testing parser existence", function () {
it('throws on non-existence', function (done) {
var mochaListener = helper.removeMochaListener();
process.once('uncaughtException', function (err) {
process.on('uncaughtException', mochaListener);
assert.equal(err.message, 'Couldn\'t find named parser nonExistingParser on this system');
return done();
});
redis.createClient({
parser: 'nonExistingParser'
});
});
});
helper.allTests({
allConnections: true
}, function(parser, ip, args) {
describe("using " + parser + " and " + ip, function () {
var client;
afterEach(function () {
client.end();
});
describe("when connected", function () {
beforeEach(function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.once("connect", function () {
client.flushdb(done);
});
});
describe('big data', function () {
// Check if the fast mode for big strings is working correct
it('safe strings that are bigger than 30000 characters', function(done) {
var str = 'foo ಠ_ಠ bar ';
while (str.length < 111111) {
str += str;
}
client.set('foo', str);
client.get('foo', function (err, res) {
assert.strictEqual(res, str);
done();
});
});
});
describe("send_command", function () {
it("omitting args should be fine in some cases", function (done) {
client.send_command("info", undefined, function(err, res) {
assert(/redis_version/.test(res));
done();
});
});
it("using another type as cb should just work as if there were no callback parameter", function (done) {
client.send_command('set', ['test', 'bla'], [true]);
client.get('test', function(err, res) {
assert.equal(res, 'bla');
done();
});
});
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.code, 'ERR');
done();
});
});
it("misusing the function should eventually throw (wrong args)", function (done) {
client.send_command('info', false, function(err, res) {
assert.equal(err.message, 'ERR Protocol error: invalid multibulk length');
done();
});
});
});
describe(".end", function () {
it('used without flush', function(done) {
var end = helper.callFuncAfter(function() {
done(new Error('failed'));
}, 20);
var cb = function(err, res) {
assert.equal(err.message, "SET can't be processed. The connection has already been closed.");
end();
};
for (var i = 0; i < 20; i++) {
if (i === 10) {
client.end();
}
client.set('foo', 'bar', cb);
}
setTimeout(done, 250);
});
it('used with flush set to true', function(done) {
var end = helper.callFuncAfter(function() {
done();
}, 20);
var cb = function(err, res) {
assert(/The connection has already been closed./.test(err.message));
end();
};
for (var i = 0; i < 20; i++) {
if (i === 10) {
client.end(true);
}
client.set('foo', 'bar', cb);
}
});
});
describe("commands after using .quit should fail", 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]
client = redis.createClient();
client.quit(function() {
client.get("foo", function(err, res) {
assert(err.message.indexOf('Redis connection gone') !== -1);
assert.strictEqual(client.offline_queue.length, 0);
done();
});
});
});
it("return an error in the callback version two", function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.quit();
setTimeout(function() {
client.get("foo", function(err, res) {
assert.strictEqual(err.message, 'GET can\'t be processed. The connection has already been closed.');
assert.strictEqual(err.command, 'GET');
assert.strictEqual(client.offline_queue.length, 0);
done();
});
}, 100);
});
it("emit an error", function (done) {
if (helper.redisProcess().spawnFailed()) this.skip();
client.quit();
client.on('error', function(err) {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
assert.strictEqual(err.command, 'SET');
assert.strictEqual(client.offline_queue.length, 0);
done();
});
setTimeout(function() {
client.set('foo', 'bar');
}, 50);
});
});
describe("when redis closes unexpectedly", function () {
it("reconnects and can retrieve the pre-existing data", function (done) {
client.on("reconnecting", function on_recon(params) {
client.on("connect", function on_connect() {
var end = helper.callFuncAfter(function () {
client.removeListener("connect", on_connect);
client.removeListener("reconnecting", on_recon);
assert.strictEqual(client.server_info.db0.keys, 2);
assert.strictEqual(Object.keys(client.server_info.db0).length, 3);
done();
}, 4);
client.get("recon 1", helper.isString("one", end));
client.get("recon 1", helper.isString("one", end));
client.get("recon 2", helper.isString("two", end));
client.get("recon 2", helper.isString("two", end));
});
});
client.set("recon 1", "one");
client.set("recon 2", "two", function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
it("reconnects properly when monitoring", function (done) {
client.on("reconnecting", function on_recon(params) {
client.on("ready", function on_ready() {
assert.strictEqual(client.monitoring, true, "monitoring after reconnect");
client.removeListener("ready", on_ready);
client.removeListener("reconnecting", on_recon);
done();
});
});
assert.strictEqual(client.monitoring, false, "monitoring off at start");
client.set("recon 1", "one");
client.monitor(function (err, res) {
assert.strictEqual(client.monitoring, true, "monitoring on after monitor()");
client.set("recon 2", "two", function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
});
// TODO: we should only have a single subscription in this this
// test but unsubscribing from the single channel indicates
// that one subscriber still exists, let's dig into this.
describe("and it's subscribed to a channel", function () {
// reconnect_select_db_after_pubsub
// Does not pass.
// "Connection in subscriber mode, only subscriber commands may be used"
it("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) {
client.on("ready", function on_connect() {
client.unsubscribe(helper.isNotError());
client.on('unsubscribe', function (channel, count) {
// we should now be out of subscriber mode.
client.set('foo', 'bar', helper.isString('OK', done));
});
});
client.set("recon 1", "one");
client.subscribe("recon channel", function (err, res) {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy();
});
});
});
describe('domain', function () {
it('allows client to be executed from within domain', function (done) {
var domain;
try {
domain = require('domain').create();
} catch (err) {
console.log("Skipping test because this version of node doesn't have domains.");
return done();
}
domain.run(function () {
client.set('domain', 'value', function (err, res) {
assert.ok(process.domain);
throw new Error('ohhhh noooo');
});
});
// this is the expected and desired behavior
domain.on('error', function (err) {
assert.strictEqual(err.message, 'ohhhh noooo');
domain.exit();
return done();
});
});
});
});
describe('monitor', function () {
it('monitors commands on all other redis clients', function (done) {
helper.serverVersionAtLeast.call(this, client, [2, 6, 0]);
var monitorClient = redis.createClient.apply(redis.createClient, args);
var responses = [];
monitorClient.monitor(function (err, res) {
client.mget("some", "keys", "foo", "bar");
client.set("json", JSON.stringify({
foo: "123",
bar: "sdflkdfsjk",
another: false
}));
});
monitorClient.on("monitor", function (time, args) {
responses.push(args);
if (responses.length === 2) {
assert.strictEqual(5, responses[0].length);
assert.strictEqual("mget", responses[0][0]);
assert.strictEqual("some", responses[0][1]);
assert.strictEqual("keys", responses[0][2]);
assert.strictEqual("foo", responses[0][3]);
assert.strictEqual("bar", responses[0][4]);
assert.strictEqual(3, responses[1].length);
assert.strictEqual("set", responses[1][0]);
assert.strictEqual("json", responses[1][1]);
assert.strictEqual('{"foo":"123","bar":"sdflkdfsjk","another":false}', responses[1][2]);
monitorClient.quit(done);
}
});
});
});
describe('idle', function () {
it('emits idle as soon as there are no outstanding commands', function (done) {
client.on('idle', function onIdle () {
client.removeListener("idle", onIdle);
client.get('foo', helper.isString('bar', done));
});
client.set('foo', 'bar');
});
});
describe('utf8', function () {
it('handles utf-8 keys', function (done) {
var utf8_sample = "ಠ_ಠ";
client.set(["utf8test", utf8_sample], helper.isString("OK"));
client.get(["utf8test"], function (err, obj) {
assert.strictEqual(utf8_sample, obj);
return done(err);
});
});
});
});
describe('unref', function () {
it('exits subprocess as soon as final command is processed', function (done) {
this.timeout(12000);
var args = config.HOST[ip] ? [config.HOST[ip], config.PORT] : [ip];
var external = fork("./test/lib/unref.js", args);
var id = setTimeout(function () {
external.kill();
return done(Error('unref subprocess timed out'));
}, 8000);
external.on("close", function (code) {
clearTimeout(id);
assert.strictEqual(code, 0);
return done();
});
});
});
describe('socket_nodelay', function () {
describe('true', function () {
var args = config.configureClient(parser, ip, {
socket_nodelay: true
});
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.quit();
client.once('end', function () {
return done();
});
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.set(["set key 1", "set val"], helper.isString("OK"));
client.set(["set key 2", "set val"], helper.isString("OK"));
client.get(["set key 1"], helper.isString("set val"));
client.get(["set key 2"], helper.isString("set val"));
client.quit();
client.once('end', function () {
return done();
});
});
});
});
describe('false', function () {
var args = config.configureClient(parser, ip, {
socket_nodelay: false
});
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(false, client.options.socket_nodelay);
client.quit();
client.once('end', function () {
return done();
});
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(false, client.options.socket_nodelay);
client.set(["set key 1", "set val"], helper.isString("OK"));
client.set(["set key 2", "set val"], helper.isString("OK"));
client.get(["set key 1"], helper.isString("set val"));
client.get(["set key 2"], helper.isString("set val"));
client.quit();
client.once('end', function () {
return done();
});
});
});
});
describe('defaults to true', function () {
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.quit();
client.once('end', function () {
return done();
});
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(redis.createClient, args);
client.on("ready", function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.set(["set key 1", "set val"], helper.isString("OK"));
client.set(["set key 2", "set val"], helper.isString("OK"));
client.get(["set key 1"], helper.isString("set val"));
client.get(["set key 2"], helper.isString("set val"));
client.quit();
client.once('end', function () {
return done();
});
});
});
});
});
describe('retry_max_delay', function () {
var args = config.configureClient(parser, ip, {
retry_max_delay: 1 // ms
});
it("sets upper bound on how long client waits before reconnecting", function (done) {
var time = new Date().getTime();
var reconnecting = false;
client = redis.createClient.apply(redis.createClient, args);
client.on('ready', function() {
if (!reconnecting) {
reconnecting = true;
client.retry_delay = 1000;
client.retry_backoff = 1;
client.stream.end();
} else {
client.end();
var lasted = new Date().getTime() - time;
assert.ok(lasted < 100);
return done();
}
});
});
});
describe('enable_offline_queue', function () {
describe('true', function () {
it("should emit drain if offline queue is flushed and nothing to buffer", function (done) {
client = redis.createClient({
parser: parser,
no_ready_check: true
});
var end = helper.callFuncAfter(done, 2);
client.set('foo', 'bar');
client.get('foo', end);
client.on('drain', function() {
assert(client.offline_queue.length === 0);
end();
});
});
it("does not return an error and enqueues operation", function (done) {
client = redis.createClient(9999, null, {
max_attempts: 0,
parser: parser
});
client.on('error', function(e) {
// ignore, b/c expecting a "can't connect" error
});
return setTimeout(function() {
client.set('foo', 'bar', function(err, result) {
// This should never be called
return done(err);
});
return setTimeout(function() {
assert.strictEqual(client.offline_queue.length, 1);
return done();
}, 25);
}, 50);
});
it("enqueues operation and keep the queue while trying to reconnect", function (done) {
client = redis.createClient(9999, null, {
max_attempts: 4,
parser: parser
});
var i = 0;
client.on('error', function(err) {
if (err.message === 'Redis connection in broken state: maximum connection attempts exceeded.') {
assert(i, 3);
assert.strictEqual(client.offline_queue.length, 0);
done();
} else {
assert.equal(err.code, 'ECONNREFUSED');
assert.equal(err.errno, 'ECONNREFUSED');
assert.equal(err.syscall, 'connect');
}
});
client.on('reconnecting', function(params) {
i++;
assert.equal(params.attempt, i);
assert.strictEqual(client.offline_queue.length, 2);
});
// Should work with either a callback or without
client.set('baz', 13);
client.set('foo', 'bar', function(err, result) {
assert(i, 3);
assert(err);
assert.strictEqual(client.offline_queue.length, 0);
});
});
it("flushes the command queue if connection is lost", function (done) {
client = redis.createClient({
parser: parser
});
client.once('ready', function() {
var multi = client.multi();
multi.config("bar");
var cb = function(err, reply) {
assert.equal(err.code, 'UNCERTAIN_STATE');
};
for (var i = 0; i < 12; i += 3) {
client.set("foo" + i, "bar" + i);
multi.set("foo" + (i + 1), "bar" + (i + 1), cb);
multi.set("foo" + (i + 2), "bar" + (i + 2));
}
multi.exec();
assert.equal(client.command_queue.length, 15);
helper.killConnection(client);
});
client.on("reconnecting", function (params) {
assert.equal(client.command_queue.length, 15);
});
client.on('error', function(err) {
if (/uncertain state/.test(err.message)) {
assert.equal(client.command_queue.length, 0);
done();
} else {
assert.equal(err.code, 'ECONNREFUSED');
assert.equal(err.errno, 'ECONNREFUSED');
assert.equal(err.syscall, 'connect');
}
});
});
});
describe('false', function () {
it('stream not writable', function(done) {
client = redis.createClient({
parser: parser,
enable_offline_queue: false
});
client.on('ready', function () {
client.stream.writable = false;
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, "SET can't be processed. Stream not writeable.");
done();
});
});
});
it("emit an error and does not enqueues operation", function (done) {
client = redis.createClient(9999, null, {
parser: parser,
max_attempts: 0,
enable_offline_queue: false
});
var end = helper.callFuncAfter(done, 3);
client.on('error', function(err) {
assert(/offline queue is deactivated|ECONNREFUSED/.test(err.message));
assert.equal(client.command_queue.length, 0);
end();
});
client.set('foo', 'bar');
assert.doesNotThrow(function () {
client.set('foo', 'bar', function (err) {
// should callback with an error
assert.ok(err);
setTimeout(end, 50);
});
});
});
it("flushes the command queue if connection is lost", function (done) {
client = redis.createClient({
parser: parser,
max_attempts: 2,
enable_offline_queue: false
});
client.once('ready', function() {
var multi = client.multi();
multi.config("bar");
var cb = function(err, reply) {
assert.equal(err.code, 'UNCERTAIN_STATE');
};
for (var i = 0; i < 12; i += 3) {
client.set("foo" + i, "bar" + i);
multi.set("foo" + (i + 1), "bar" + (i + 1), cb);
multi.set("foo" + (i + 2), "bar" + (i + 2));
}
multi.exec();
assert.equal(client.command_queue.length, 15);
helper.killConnection(client);
});
client.on("reconnecting", function (params) {
assert.equal(client.command_queue.length, 15);
});
client.on('error', function(err) {
if (err.code === 'UNCERTAIN_STATE') {
assert.equal(client.command_queue.length, 0);
done();
} else {
assert.equal(err.code, 'ECONNREFUSED');
assert.equal(err.errno, 'ECONNREFUSED');
assert.equal(err.syscall, 'connect');
}
});
});
});
});
});
});
});