You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-01 16:46:54 +03:00
Reduce timeouts if possible Extend timeouts if needed (windows tests need their time) Don't expose the redis socket to others than the owner Don't create the stunnel log
1067 lines
54 KiB
JavaScript
1067 lines
54 KiB
JavaScript
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var config = require('./lib/config');
|
|
var helper = require('./helper');
|
|
var utils = require('../lib/utils');
|
|
var fork = require('child_process').fork;
|
|
var redis = config.redis;
|
|
|
|
describe('The node_redis client', function () {
|
|
|
|
helper.allTests(function (parser, ip, args) {
|
|
|
|
describe('using ' + parser + ' and ' + ip, function () {
|
|
var client;
|
|
|
|
afterEach(function () {
|
|
client.end(true);
|
|
});
|
|
|
|
describe('when connected', function () {
|
|
beforeEach(function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
client.once('connect', function () {
|
|
client.flushdb(done);
|
|
});
|
|
});
|
|
|
|
describe('duplicate', function () {
|
|
it('check if all options got copied properly', function (done) {
|
|
client.selected_db = 2;
|
|
var client2 = client.duplicate();
|
|
assert.strictEqual(client2.selected_db, 2);
|
|
assert(client.connected);
|
|
assert(!client2.connected);
|
|
for (var elem in client.options) {
|
|
if (client.options.hasOwnProperty(elem)) {
|
|
assert.strictEqual(client2.options[elem], client.options[elem]);
|
|
}
|
|
}
|
|
client2.on('ready', function () {
|
|
client2.end(true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('check if all new options replaced the old ones', function (done) {
|
|
var client2 = client.duplicate({
|
|
no_ready_check: true
|
|
});
|
|
assert(client.connected);
|
|
assert(!client2.connected);
|
|
assert.strictEqual(client.options.no_ready_check, undefined);
|
|
assert.strictEqual(client2.options.no_ready_check, true);
|
|
assert.notDeepEqual(client.options, client2.options);
|
|
for (var elem in client.options) {
|
|
if (client.options.hasOwnProperty(elem)) {
|
|
if (elem !== 'no_ready_check') {
|
|
assert.strictEqual(client2.options[elem], client.options[elem]);
|
|
}
|
|
}
|
|
}
|
|
client2.on('ready', function () {
|
|
client2.end(true);
|
|
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();
|
|
});
|
|
});
|
|
|
|
it('safe strings that are bigger than 30000 characters with multi', function (done) {
|
|
var str = 'foo ಠ_ಠ bar ';
|
|
while (str.length < 111111) {
|
|
str += str;
|
|
}
|
|
var called = false;
|
|
var temp = client.writeBuffers.bind(client);
|
|
assert(String(client.writeBuffers) !== String(client.writeDefault));
|
|
client.writeBuffers = function (data) {
|
|
called = true;
|
|
// To increase write performance for strings the value is converted to a buffer
|
|
assert(String(client.writeBuffers) === String(client.writeDefault));
|
|
temp(data);
|
|
};
|
|
client.multi().set('foo', str).get('foo', function (err, res) {
|
|
assert.strictEqual(res, str);
|
|
}).exec(function (err, res) {
|
|
assert(called);
|
|
assert.strictEqual(res[1], str);
|
|
done();
|
|
});
|
|
assert(String(client.writeBuffers) !== String(client.writeDefault));
|
|
});
|
|
});
|
|
|
|
describe('send_command', function () {
|
|
|
|
it('omitting args should be fine', function (done) {
|
|
client.server_info = {};
|
|
client.send_command('info');
|
|
client.send_command('ping', function (err, res) {
|
|
assert.strictEqual(res, 'PONG');
|
|
// Check if the previous info command used the internal individual info command
|
|
assert.notDeepEqual(client.server_info, {});
|
|
client.server_info = {};
|
|
});
|
|
client.send_command('info', null, undefined);
|
|
client.send_command('ping', null, function (err, res) {
|
|
assert.strictEqual(res, 'PONG');
|
|
// Check if the previous info command used the internal individual info command
|
|
assert.notDeepEqual(client.server_info, {});
|
|
client.server_info = {};
|
|
});
|
|
client.send_command('info', undefined, undefined);
|
|
client.send_command('ping', function (err, res) {
|
|
assert.strictEqual(res, 'PONG');
|
|
// Check if the previous info command used the internal individual info command
|
|
assert.notDeepEqual(client.server_info, {});
|
|
client.server_info = {};
|
|
});
|
|
client.send_command('info', undefined, function (err, res) {
|
|
assert(/redis_version/.test(res));
|
|
// The individual info command should also be called by using send_command
|
|
// console.log(info, client.server_info);
|
|
assert.notDeepEqual(client.server_info, {});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('using multi with send_command should work as individual command instead of using the internal multi', function (done) {
|
|
// This is necessary to keep backwards compatibility and it is the only way to handle multis as you want in node_redis
|
|
client.send_command('multi');
|
|
client.send_command('set', ['foo', 'bar'], helper.isString('QUEUED'));
|
|
client.get('foo');
|
|
client.exec(function (err, res) { // exec is not manipulated if not fired by the individual multi command
|
|
// As the multi command is handled individually by the user he also has to handle the return value
|
|
assert.strictEqual(res[0].toString(), 'OK');
|
|
assert.strictEqual(res[1].toString(), 'bar');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('multi should be handled special', function (done) {
|
|
client.send_command('multi', undefined, helper.isString('OK'));
|
|
var args = ['test', 'bla'];
|
|
client.send_command('set', args, helper.isString('QUEUED'));
|
|
assert.deepEqual(args, ['test', 'bla']); // Check args manipulation
|
|
client.get('test', helper.isString('QUEUED'));
|
|
client.exec(function (err, res) {
|
|
// As the multi command is handled individually by the user he also has to handle the return value
|
|
assert.strictEqual(res[0].toString(), 'OK');
|
|
assert.strictEqual(res[1].toString(), 'bla');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('using another type as cb should throw', function () {
|
|
try {
|
|
client.send_command('set', ['test', 'bla'], [true]);
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Array" for callback function');
|
|
}
|
|
try {
|
|
client.send_command('set', ['test', 'bla'], null);
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "null" for callback function');
|
|
}
|
|
});
|
|
|
|
it('command argument has to be of type string', function () {
|
|
try {
|
|
client.send_command(true, ['test', 'bla'], function () {});
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name');
|
|
}
|
|
try {
|
|
client.send_command(undefined, ['test', 'bla'], function () {});
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "undefined" for command name');
|
|
}
|
|
try {
|
|
client.send_command(null, ['test', 'bla'], function () {});
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "null" for command name');
|
|
}
|
|
});
|
|
|
|
it('args may only be of type Array or undefined', function () {
|
|
try {
|
|
client.send_command('info', 123);
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Number" for args');
|
|
}
|
|
});
|
|
|
|
it('passing a callback as args and as callback should throw', function () {
|
|
try {
|
|
client.send_command('info', function a () {}, function b () {});
|
|
throw new Error('failed');
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Function" for args');
|
|
}
|
|
});
|
|
|
|
it('multi should be handled special', function (done) {
|
|
client.send_command('multi', undefined, helper.isString('OK'));
|
|
var args = ['test', 'bla'];
|
|
client.send_command('set', args, helper.isString('QUEUED'));
|
|
assert.deepEqual(args, ['test', 'bla']); // Check args manipulation
|
|
client.get('test', helper.isString('QUEUED'));
|
|
client.exec(function (err, res) {
|
|
// As the multi command is handled individually by the user he also has to handle the return value
|
|
assert.strictEqual(res[0].toString(), 'OK');
|
|
assert.strictEqual(res[1].toString(), 'bla');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('the args array may contain a arbitrary number of arguments', function (done) {
|
|
client.send_command('mset', ['foo', 1, 'bar', 2, 'baz', 3], helper.isString('OK'));
|
|
client.mget(['foo', 'bar', 'baz'], function (err, res) {
|
|
// As the multi command is handled individually by the user he also has to handle the return value
|
|
assert.strictEqual(res[0].toString(), '1');
|
|
assert.strictEqual(res[1].toString(), '2');
|
|
assert.strictEqual(res[2].toString(), '3');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('send_command with callback as args', function (done) {
|
|
client.send_command('abcdef', function (err, res) {
|
|
assert.strictEqual(err.message, "ERR unknown command 'abcdef'");
|
|
done();
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
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');
|
|
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) {
|
|
var finished = false;
|
|
var end = helper.callFuncAfter(function () {
|
|
if (!finished) {
|
|
done(new Error('failed'));
|
|
}
|
|
}, 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();
|
|
}
|
|
client.set('foo', 'bar', cb);
|
|
}
|
|
client.on('warning', function () {}); // Ignore deprecation message
|
|
setTimeout(function () {
|
|
finished = true;
|
|
done();
|
|
}, 25);
|
|
});
|
|
|
|
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.stream.write('foo'); // Trigger an error on the closed stream that we ignore
|
|
}
|
|
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 using '/tmp/redis.sock'.
|
|
// 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) {
|
|
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();
|
|
});
|
|
}, 50);
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("and it's subscribed to a channel", function () {
|
|
// "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.
|
|
assert.strictEqual(channel, 'recon channel');
|
|
assert.strictEqual(count, 0);
|
|
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();
|
|
});
|
|
});
|
|
|
|
it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', function (done) {
|
|
client.on('ready', function on_connect () {
|
|
client.unsubscribe('recon channel', helper.isNotError());
|
|
|
|
client.on('unsubscribe', function (channel, count) {
|
|
// we should now be out of subscriber mode.
|
|
assert.strictEqual(channel, 'recon channel');
|
|
assert.strictEqual(count, 0);
|
|
client.set('foo', 'bar', helper.isString('OK', done));
|
|
});
|
|
});
|
|
|
|
client.set('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 = require('domain').create();
|
|
|
|
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();
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('catches all errors from within the domain', function (done) {
|
|
var domain = require('domain').create();
|
|
|
|
domain.run(function () {
|
|
// Trigger an error within the domain
|
|
client.end(true);
|
|
client.set('domain', 'value');
|
|
});
|
|
|
|
domain.on('error', function (err) {
|
|
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
|
|
domain.exit();
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('monitor', function () {
|
|
it('monitors commands on all redis clients and works in the correct order', function (done) {
|
|
var monitorClient = redis.createClient.apply(null, args);
|
|
var responses = [];
|
|
var end = helper.callFuncAfter(done, 5);
|
|
|
|
monitorClient.set('foo', 'bar');
|
|
monitorClient.flushdb();
|
|
monitorClient.monitor(function (err, res) {
|
|
assert.strictEqual(res, 'OK');
|
|
client.mget('some', 'keys', 'foo', 'bar');
|
|
client.set('json', JSON.stringify({
|
|
foo: '123',
|
|
bar: 'sdflkdfsjk',
|
|
another: false
|
|
}));
|
|
monitorClient.get('baz', function (err, res) {
|
|
assert.strictEqual(res, null);
|
|
end(err);
|
|
});
|
|
monitorClient.set('foo', 'bar" "s are " " good!"', function (err, res) {
|
|
assert.strictEqual(res, 'OK');
|
|
end(err);
|
|
});
|
|
monitorClient.mget('foo', 'baz', function (err, res) {
|
|
assert.strictEqual(res[0], 'bar" "s are " " good!"');
|
|
assert.strictEqual(res[1], null);
|
|
end(err);
|
|
});
|
|
monitorClient.subscribe('foo', 'baz', function (err, res) {
|
|
// The return value might change in v.3
|
|
// assert.strictEqual(res, 'baz');
|
|
// TODO: Fix the return value of subscribe calls
|
|
end(err);
|
|
});
|
|
});
|
|
|
|
monitorClient.on('monitor', function (time, args, rawOutput) {
|
|
responses.push(args);
|
|
assert(utils.monitor_regex.test(rawOutput), rawOutput);
|
|
if (responses.length === 6) {
|
|
assert.deepEqual(responses[0], ['mget', 'some', 'keys', 'foo', 'bar']);
|
|
assert.deepEqual(responses[1], ['set', 'json', '{"foo":"123","bar":"sdflkdfsjk","another":false}']);
|
|
assert.deepEqual(responses[2], ['get', 'baz']);
|
|
assert.deepEqual(responses[3], ['set', 'foo', 'bar" "s are " " good!"']);
|
|
assert.deepEqual(responses[4], ['mget', 'foo', 'baz']);
|
|
assert.deepEqual(responses[5], ['subscribe', 'foo', 'baz']);
|
|
monitorClient.quit(end);
|
|
}
|
|
});
|
|
});
|
|
|
|
it('monitors returns strings in the rawOutput even with return_buffers activated', function (done) {
|
|
var monitorClient = redis.createClient({
|
|
return_buffers: true
|
|
});
|
|
|
|
monitorClient.MONITOR(function (err, res) {
|
|
assert.strictEqual(res.inspect(), new Buffer('OK').inspect());
|
|
client.mget('hello', new Buffer('world'));
|
|
});
|
|
|
|
monitorClient.on('monitor', function (time, args, rawOutput) {
|
|
assert.strictEqual(typeof rawOutput, 'string');
|
|
assert(utils.monitor_regex.test(rawOutput), rawOutput);
|
|
assert.deepEqual(args, ['mget', 'hello', 'world']);
|
|
// Quit immediatly ends monitoring mode and therefor does not stream back the quit command
|
|
monitorClient.quit(done);
|
|
});
|
|
});
|
|
|
|
it('monitors reconnects properly and works with the offline queue', function (done) {
|
|
var i = 0;
|
|
client.MONITOR(helper.isString('OK'));
|
|
client.mget('hello', 'world');
|
|
client.on('monitor', function (time, args, rawOutput) {
|
|
assert(utils.monitor_regex.test(rawOutput), rawOutput);
|
|
assert.deepEqual(args, ['mget', 'hello', 'world']);
|
|
if (i++ === 2) {
|
|
// End after two reconnects
|
|
return done();
|
|
}
|
|
client.stream.destroy();
|
|
client.mget('hello', 'world');
|
|
});
|
|
});
|
|
|
|
it('monitor does not activate if the command could not be processed properly', function (done) {
|
|
client.MONITOR(function (err, res) {
|
|
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
|
|
});
|
|
client.on('error', function (err) {}); // Ignore error here
|
|
client.stream.destroy();
|
|
client.on('monitor', function (time, args, rawOutput) {
|
|
done(new Error('failed')); // Should not be activated
|
|
});
|
|
client.on('reconnecting', function () {
|
|
client.get('foo', function (err, res) {
|
|
assert(!err);
|
|
assert.strictEqual(client.monitoring, false);
|
|
setTimeout(done, 10); // The monitor command might be returned a tiny bit later
|
|
});
|
|
});
|
|
});
|
|
|
|
it('monitors works in combination with the pub sub mode and the offline queue', function (done) {
|
|
var responses = [];
|
|
var pub = redis.createClient();
|
|
pub.on('ready', function () {
|
|
client.MONITOR(function (err, res) {
|
|
assert.strictEqual(res, 'OK');
|
|
pub.get('foo', helper.isNull());
|
|
});
|
|
client.subscribe('/foo', '/bar');
|
|
client.unsubscribe('/bar');
|
|
setTimeout(function () {
|
|
client.stream.destroy();
|
|
client.once('ready', function () {
|
|
pub.publish('/foo', 'hello world');
|
|
});
|
|
client.set('foo', 'bar', helper.isError());
|
|
client.subscribe('baz');
|
|
client.unsubscribe('baz');
|
|
}, 150);
|
|
var called = false;
|
|
client.on('monitor', function (time, args, rawOutput) {
|
|
responses.push(args);
|
|
assert(utils.monitor_regex.test(rawOutput), rawOutput);
|
|
if (responses.length === 7) {
|
|
assert.deepEqual(responses[0], ['subscribe', '/foo', '/bar']);
|
|
assert.deepEqual(responses[1], ['unsubscribe', '/bar']);
|
|
assert.deepEqual(responses[2], ['get', 'foo']);
|
|
assert.deepEqual(responses[3], ['subscribe', '/foo']);
|
|
assert.deepEqual(responses[4], ['subscribe', 'baz']);
|
|
assert.deepEqual(responses[5], ['unsubscribe', 'baz']);
|
|
assert.deepEqual(responses[6], ['publish', '/foo', 'hello world']);
|
|
// The publish is called right after the reconnect and the monitor is called before the message is emitted.
|
|
// Therefor we have to wait till the next tick
|
|
process.nextTick(function () {
|
|
assert(called);
|
|
client.quit(done);
|
|
pub.end(false);
|
|
});
|
|
}
|
|
});
|
|
client.on('message', function (channel, msg) {
|
|
assert.strictEqual(channel, '/foo');
|
|
assert.strictEqual(msg, 'hello world');
|
|
called = true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('idle', function () {
|
|
it('emits idle as soon as there are no outstanding commands', function (done) {
|
|
var end = helper.callFuncAfter(done, 2);
|
|
client.on('warning', function (msg) {
|
|
assert.strictEqual(
|
|
msg,
|
|
'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' +
|
|
'If you rely on this feature please open a new ticket in node_redis with your use case'
|
|
);
|
|
end();
|
|
});
|
|
client.on('idle', function onIdle () {
|
|
client.removeListener('idle', onIdle);
|
|
client.get('foo', helper.isString('bar', end));
|
|
});
|
|
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);
|
|
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();
|
|
done(Error('unref subprocess timed out'));
|
|
}, 8000);
|
|
|
|
external.on('close', function (code) {
|
|
clearTimeout(id);
|
|
assert.strictEqual(code, 0);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('execution order / fire query while loading', function () {
|
|
it('keep execution order for commands that may fire while redis is still loading', function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
var fired = false;
|
|
client.set('foo', 'bar', function (err, res) {
|
|
assert(fired === false);
|
|
done();
|
|
});
|
|
client.info(function (err, res) {
|
|
fired = true;
|
|
});
|
|
});
|
|
|
|
it('should fire early', function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
var fired = false;
|
|
client.info(function (err, res) {
|
|
fired = true;
|
|
});
|
|
client.set('foo', 'bar', function (err, res) {
|
|
assert(fired);
|
|
done();
|
|
});
|
|
assert.strictEqual(client.offline_queue.length, 1);
|
|
assert.strictEqual(client.command_queue.length, 1);
|
|
client.on('connect', function () {
|
|
assert.strictEqual(client.offline_queue.length, 1);
|
|
assert.strictEqual(client.command_queue.length, 1);
|
|
});
|
|
client.on('ready', function () {
|
|
assert.strictEqual(client.offline_queue.length, 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
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(null, args);
|
|
client.on('ready', function () {
|
|
assert.strictEqual(true, client.options.socket_nodelay);
|
|
client.quit(done);
|
|
});
|
|
});
|
|
|
|
it('client is functional', function (done) {
|
|
client = redis.createClient.apply(null, 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(done);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('false', function () {
|
|
var args = config.configureClient(parser, ip, {
|
|
socket_nodelay: false
|
|
});
|
|
|
|
it("fires client.on('ready')", function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
client.on('ready', function () {
|
|
assert.strictEqual(false, client.options.socket_nodelay);
|
|
client.quit(done);
|
|
});
|
|
});
|
|
|
|
it('client is functional', function (done) {
|
|
client = redis.createClient.apply(null, 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(done);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('defaults to true', function () {
|
|
|
|
it("fires client.on('ready')", function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
client.on('ready', function () {
|
|
assert.strictEqual(true, client.options.socket_nodelay);
|
|
client.quit(done);
|
|
});
|
|
});
|
|
|
|
it('client is functional', function (done) {
|
|
client = redis.createClient.apply(null, 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(done);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('retry_max_delay', function () {
|
|
it('sets upper bound on how long client waits before reconnecting', function (done) {
|
|
var time;
|
|
var timeout = process.platform !== 'win32' ? 20 : 100;
|
|
|
|
client = redis.createClient.apply(null, config.configureClient(parser, ip, {
|
|
retry_max_delay: 1 // ms
|
|
}));
|
|
client.on('ready', function () {
|
|
if (this.times_connected === 1) {
|
|
this.stream.end();
|
|
time = Date.now();
|
|
} else {
|
|
done();
|
|
}
|
|
});
|
|
client.on('reconnecting', function () {
|
|
time = Date.now() - time;
|
|
assert(time < timeout, 'The reconnect should not have taken longer than ' + timeout + ' but it took ' + time);
|
|
});
|
|
client.on('error', function (err) {
|
|
// This is rare but it might be triggered.
|
|
// So let's have a robust test
|
|
assert.strictEqual(err.code, 'ECONNRESET');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('protocol error', function () {
|
|
|
|
it('should gracefully recover and only fail on the already send commands', function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
client.on('error', function (err) {
|
|
assert.strictEqual(err.message, 'Protocol error, got "a" as reply type byte');
|
|
// After the hard failure work properly again. The set should have been processed properly too
|
|
client.get('foo', function (err, res) {
|
|
assert.strictEqual(res, 'bar');
|
|
done();
|
|
});
|
|
});
|
|
client.once('ready', function () {
|
|
client.set('foo', 'bar', function (err, res) {
|
|
assert.strictEqual(err.message, 'Protocol error, got "a" as reply type byte');
|
|
});
|
|
// Fail the set answer. Has no corresponding command obj and will therefor land in the error handler and set
|
|
client.reply_parser.execute(new Buffer('a*1\r*1\r$1`zasd\r\na'));
|
|
});
|
|
});
|
|
});
|
|
|
|
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, 3);
|
|
client.set('foo', 'bar');
|
|
client.get('foo', end);
|
|
client.on('warning', function (msg) {
|
|
assert.strictEqual(
|
|
msg,
|
|
'The drain event listener is deprecated and will be removed in v.3.0.0.\n' +
|
|
'If you want to keep on listening to this event please listen to the stream drain event directly.'
|
|
);
|
|
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
|
|
});
|
|
var finished = false;
|
|
client.on('error', function (e) {
|
|
// ignore, b/c expecting a "can't connect" error
|
|
});
|
|
|
|
setTimeout(function () {
|
|
client.set('foo', 'bar', function (err, result) {
|
|
if (!finished) done(err);
|
|
assert.strictEqual(err.message, "The command can't be processed. The connection has already been closed.");
|
|
});
|
|
|
|
setTimeout(function () {
|
|
assert.strictEqual(client.offline_queue.length, 1);
|
|
finished = true;
|
|
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(params.times_connected, 0);
|
|
assert(params.error instanceof Error);
|
|
assert(typeof params.total_retry_time === 'number');
|
|
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('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.destroy();
|
|
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('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');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|