You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-09 00:22:08 +03:00
Added individual error classes Don't silently fail for commands without callback from now on General polishing (e.g. better error messages) Fix typos
718 lines
34 KiB
JavaScript
718 lines
34 KiB
JavaScript
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var config = require('./lib/config');
|
|
var helper = require('./helper');
|
|
var utils = require('../lib/utils');
|
|
var redis = config.redis;
|
|
var zlib = require('zlib');
|
|
var client;
|
|
|
|
describe("The 'multi' method", function () {
|
|
|
|
afterEach(function () {
|
|
client.end(true);
|
|
});
|
|
|
|
describe('regression test', function () {
|
|
it('saved buffers with charsets different than utf-8 (issue #913)', function (done) {
|
|
this.timeout(12000); // Windows tests on 0.10 are slow
|
|
client = redis.createClient();
|
|
|
|
var end = helper.callFuncAfter(done, 100);
|
|
|
|
// Some random object created from http://beta.json-generator.com/
|
|
var test_obj = {
|
|
'_id': '5642c4c33d4667c4a1fefd99', 'index': 0, 'guid': '5baf1f1c-7621-41e7-ae7a-f8c6f3199b0f', 'isActive': true,
|
|
'balance': '$1,028.63', 'picture': 'http://placehold.it/32x32', 'age': 31, 'eyeColor': 'green', 'name': {'first': 'Shana', 'last': 'Long'},
|
|
'company': 'MANGLO', 'email': 'shana.long@manglo.us', 'phone': '+1 (926) 405-3105', 'address': '747 Dank Court, Norfolk, Ohio, 1112',
|
|
'about': 'Eu pariatur in nisi occaecat enim qui consequat nostrud cupidatat id. ' +
|
|
'Commodo commodo dolore esse irure minim quis deserunt anim laborum aute deserunt et est. Quis nisi laborum deserunt nisi quis.',
|
|
'registered': 'Friday, April 18, 2014 9:56 AM', 'latitude': '74.566613', 'longitude': '-11.660432', 'tags': [7, 'excepteur'],
|
|
'range': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'friends': [3, {'id': 1, 'name': 'Schultz Dyer'}],
|
|
'greeting': 'Hello, Shana! You have 5 unread messages.', 'favoriteFruit': 'strawberry'
|
|
};
|
|
|
|
function run () {
|
|
if (end() === true) {
|
|
return;
|
|
}
|
|
// To demonstrate a big payload for hash set field values, let's create a big array
|
|
var test_arr = [];
|
|
var i = 0;
|
|
for (; i < 80; i++) {
|
|
var new_obj = JSON.parse(JSON.stringify(test_obj));
|
|
test_arr.push(new_obj);
|
|
}
|
|
|
|
var json = JSON.stringify(test_arr);
|
|
zlib.deflate(new Buffer(json), function (err, buffer) {
|
|
if (err) {
|
|
done(err);
|
|
return;
|
|
}
|
|
|
|
var multi = client.multi();
|
|
multi.del('SOME_KEY');
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
multi.hset('SOME_KEY', 'SOME_FIELD' + i, buffer);
|
|
}
|
|
multi.exec(function (err, res) {
|
|
if (err) {
|
|
done(err);
|
|
return;
|
|
}
|
|
run();
|
|
});
|
|
});
|
|
}
|
|
run();
|
|
});
|
|
});
|
|
|
|
describe('pipeline limit', function () {
|
|
|
|
it('do not exceed maximum string size', function (done) {
|
|
this.timeout(process.platform !== 'win32' ? 10000 : 35000); // Windows tests are horribly slow
|
|
// Triggers a RangeError: Invalid string length if not handled properly
|
|
client = redis.createClient();
|
|
var multi = client.multi();
|
|
var i = Math.pow(2, 28);
|
|
while (i > 0) {
|
|
i -= 10230;
|
|
multi.set('foo' + i, 'bar' + new Array(1024).join('1234567890'));
|
|
}
|
|
client.on('ready', function () {
|
|
multi.exec(function (err, res) {
|
|
assert.strictEqual(res.length, 26241);
|
|
});
|
|
client.flushdb(done);
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
helper.allTests(function (parser, ip, args) {
|
|
|
|
describe('using ' + parser + ' and ' + ip, function () {
|
|
|
|
describe('when not connected', function () {
|
|
|
|
beforeEach(function (done) {
|
|
var end = helper.callFuncAfter(done, 2);
|
|
client = redis.createClient.apply(null, args);
|
|
client.once('ready', function () {
|
|
client.quit(end);
|
|
});
|
|
client.once('end', end);
|
|
});
|
|
|
|
it('reports an error', function (done) {
|
|
var multi = client.multi();
|
|
var notBuffering = multi.exec(function (err, res) {
|
|
assert(err.message.match(/The connection is already closed/));
|
|
done();
|
|
});
|
|
assert.strictEqual(notBuffering, false);
|
|
});
|
|
|
|
it('reports an error if promisified', function () {
|
|
return client.multi().execAsync().catch(function (err) {
|
|
assert(err.message.match(/The connection is already closed/));
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when connected', function () {
|
|
|
|
beforeEach(function () {
|
|
client = redis.createClient.apply(null, args);
|
|
});
|
|
|
|
describe('monitor and transactions do not work together', function () {
|
|
|
|
it('results in a execabort', function (done) {
|
|
// Check that transactions in combination with monitor result in an error
|
|
client.monitor(function (e) {
|
|
client.on('error', function (err) {
|
|
assert.strictEqual(err.code, 'EXECABORT');
|
|
done();
|
|
});
|
|
var multi = client.multi();
|
|
multi.set('hello', 'world');
|
|
multi.exec();
|
|
});
|
|
});
|
|
|
|
it('results in a execabort #2', function (done) {
|
|
// Check that using monitor with a transactions results in an error
|
|
client.multi().set('foo', 'bar').monitor().exec(function (err, res) {
|
|
assert.strictEqual(err.code, 'EXECABORT');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('sanity check', function (done) {
|
|
// Remove the listener and add it back again after the error
|
|
var mochaListener = helper.removeMochaListener();
|
|
process.on('uncaughtException', function (err) {
|
|
helper.removeMochaListener();
|
|
process.on('uncaughtException', mochaListener);
|
|
done();
|
|
});
|
|
// Check if Redis still has the error
|
|
client.monitor();
|
|
client.send_command('multi');
|
|
client.send_command('set', ['foo', 'bar']);
|
|
client.send_command('get', ['foo']);
|
|
client.send_command('exec', function (err, res) {
|
|
// res[0] is going to be the monitor result of set
|
|
// res[1] is going to be the result of the set command
|
|
assert(utils.monitor_regex.test(res[0]));
|
|
assert.strictEqual(res[1], 'OK');
|
|
assert.strictEqual(res.length, 2);
|
|
client.end(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('executes a pipelined multi properly in combination with the offline queue', function (done) {
|
|
var multi1 = client.multi();
|
|
multi1.set('m1', '123');
|
|
multi1.get('m1');
|
|
multi1.exec(done);
|
|
assert.strictEqual(client.offline_queue.length, 4);
|
|
});
|
|
|
|
it('executes a pipelined multi properly after a reconnect in combination with the offline queue', function (done) {
|
|
client.once('ready', function () {
|
|
client.stream.destroy();
|
|
var called = false;
|
|
var multi1 = client.multi();
|
|
multi1.set('m1', '123');
|
|
multi1.get('m1');
|
|
multi1.exec(function (err, res) {
|
|
assert(!err);
|
|
called = true;
|
|
});
|
|
client.once('ready', function () {
|
|
var multi1 = client.multi();
|
|
multi1.set('m2', '456');
|
|
multi1.get('m2');
|
|
multi1.exec(function (err, res) {
|
|
assert(called);
|
|
assert(!err);
|
|
assert.strictEqual(res[1], '456');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when connection is broken', function () {
|
|
|
|
it('return an error even if connection is in broken mode if callback is present', function (done) {
|
|
client = redis.createClient({
|
|
host: 'somewhere',
|
|
port: 6379,
|
|
max_attempts: 1
|
|
});
|
|
|
|
client.on('error', function (err) {
|
|
if (/Redis connection in broken state/.test(err.message)) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
|
|
assert(/Redis connection in broken state/.test(err.message));
|
|
assert.strictEqual(err.errors.length, 2);
|
|
assert.strictEqual(err.errors[0].args.length, 2);
|
|
});
|
|
});
|
|
|
|
it('does not emit an error twice if connection is in broken mode with no callback', function (done) {
|
|
client = redis.createClient({
|
|
host: 'somewhere',
|
|
port: 6379,
|
|
max_attempts: 1
|
|
});
|
|
|
|
client.on('error', function (err) {
|
|
// Results in multiple done calls if test fails
|
|
if (/Redis connection in broken state/.test(err.message)) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec();
|
|
});
|
|
});
|
|
|
|
describe('when ready', function () {
|
|
|
|
beforeEach(function (done) {
|
|
client = redis.createClient.apply(null, args);
|
|
client.once('ready', function () {
|
|
client.flushdb(function (err) {
|
|
return done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('returns an empty result array', function (done) {
|
|
var multi = client.multi();
|
|
var notBuffering = multi.exec(function (err, res) {
|
|
assert.strictEqual(err, null);
|
|
assert.strictEqual(res.length, 0);
|
|
done();
|
|
});
|
|
assert.strictEqual(notBuffering, true);
|
|
});
|
|
|
|
it('runs normal calls in-between multis', function (done) {
|
|
var multi1 = client.multi();
|
|
multi1.set('m1', '123');
|
|
client.set('m2', '456', done);
|
|
});
|
|
|
|
it('runs simultaneous multis with the same client', function (done) {
|
|
var end = helper.callFuncAfter(done, 2);
|
|
|
|
var multi1 = client.multi();
|
|
multi1.set('m1', '123');
|
|
multi1.get('m1');
|
|
|
|
var multi2 = client.multi();
|
|
multi2.set('m2', '456');
|
|
multi2.get('m2');
|
|
|
|
multi1.exec(end);
|
|
multi2.exec(function (err, res) {
|
|
assert.strictEqual(res[1], '456');
|
|
end();
|
|
});
|
|
});
|
|
|
|
it('runs simultaneous multis with the same client version 2', function (done) {
|
|
var end = helper.callFuncAfter(done, 2);
|
|
var multi2 = client.multi();
|
|
var multi1 = client.multi();
|
|
|
|
multi2.set('m2', '456');
|
|
multi1.set('m1', '123');
|
|
multi1.get('m1');
|
|
multi2.get('m1');
|
|
multi2.ping();
|
|
|
|
multi1.exec(end);
|
|
multi2.exec(function (err, res) {
|
|
assert.strictEqual(res[1], '123');
|
|
end();
|
|
});
|
|
});
|
|
|
|
it('roles back a transaction when one command in a sequence of commands fails', function (done) {
|
|
var multi1, multi2;
|
|
// Provoke an error at queue time
|
|
multi1 = client.MULTI();
|
|
multi1.mset('multifoo', '10', 'multibar', '20', helper.isString('OK'));
|
|
|
|
multi1.set('foo2', helper.isError());
|
|
multi1.incr('multifoo');
|
|
multi1.incr('multibar');
|
|
multi1.exec(function () {
|
|
// Redis 2.6.5+ will abort transactions with errors
|
|
// see: http://redis.io/topics/transactions
|
|
var multibar_expected = 1;
|
|
var multifoo_expected = 1;
|
|
// Confirm that the previous command, while containing an error, still worked.
|
|
multi2 = client.multi();
|
|
multi2.incr('multibar', helper.isNumber(multibar_expected));
|
|
multi2.incr('multifoo', helper.isNumber(multifoo_expected));
|
|
multi2.exec(function (err, replies) {
|
|
assert.strictEqual(multibar_expected, replies[0]);
|
|
assert.strictEqual(multifoo_expected, replies[1]);
|
|
return done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('roles back a transaction when one command in an array of commands fails', function (done) {
|
|
// test nested multi-bulk replies
|
|
client.multi([
|
|
['mget', 'multifoo', 'multibar', function (err, res) {
|
|
assert.strictEqual(2, res.length);
|
|
assert.strictEqual(0, +res[0]);
|
|
assert.strictEqual(0, +res[1]);
|
|
}],
|
|
['set', 'foo2', helper.isError()],
|
|
['incr', 'multifoo'],
|
|
['incr', 'multibar']
|
|
]).exec(function (err, replies) {
|
|
assert.notEqual(err, null);
|
|
assert.equal(replies, undefined);
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('handles multiple operations being applied to a set', function (done) {
|
|
client.sadd('some set', 'mem 1');
|
|
client.sadd(['some set', 'mem 2']);
|
|
client.sadd('some set', 'mem 3');
|
|
client.sadd('some set', 'mem 4');
|
|
|
|
// make sure empty mb reply works
|
|
client.del('some missing set');
|
|
client.smembers('some missing set', function (err, reply) {
|
|
// make sure empty mb reply works
|
|
assert.strictEqual(0, reply.length);
|
|
});
|
|
|
|
// test nested multi-bulk replies with empty mb elements.
|
|
client.multi([
|
|
['smembers', ['some set']],
|
|
['del', 'some set'],
|
|
['smembers', 'some set']
|
|
])
|
|
.scard('some set')
|
|
.exec(function (err, replies) {
|
|
assert.strictEqual(4, replies[0].length);
|
|
assert.strictEqual(0, replies[2].length);
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('allows multiple operations to be performed using constructor with all kinds of syntax', function (done) {
|
|
var now = Date.now();
|
|
var arr = ['multihmset', 'multibar', 'multibaz'];
|
|
var arr2 = ['some manner of key', 'otherTypes'];
|
|
var arr3 = [5768, 'multibarx', 'multifoox'];
|
|
var arr4 = ['mset', [578, 'multibar'], helper.isString('OK')];
|
|
client.multi([
|
|
arr4,
|
|
[['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3'], helper.isString('OK')],
|
|
['hmset', arr],
|
|
[['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test'], helper.isString('OK')],
|
|
['hmset', ['multihmset', 'multibar', 'multifoo'], helper.isString('OK')],
|
|
['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'], undefined], // undefined is used as a explicit not set callback variable
|
|
['hmset', 'multihmset', ['multibar', 'multibaz'], helper.isString('OK')],
|
|
])
|
|
.hmget(now, 123456789, 'otherTypes')
|
|
.hmget('key2', arr2, function noop () {})
|
|
.hmget(['multihmset2', 'some manner of key', 'multibar3'])
|
|
.mget('multifoo2', ['multifoo3', 'multifoo'], function (err, res) {
|
|
assert(res[0], 'multifoo3');
|
|
assert(res[1], 'multifoo');
|
|
})
|
|
.exec(function (err, replies) {
|
|
assert.equal(arr.length, 3);
|
|
assert.equal(arr2.length, 2);
|
|
assert.equal(arr3.length, 3);
|
|
assert.equal(arr4.length, 3);
|
|
assert.strictEqual(null, err);
|
|
assert.equal(replies[10][1], '555');
|
|
assert.equal(replies[11][0], 'a type of value');
|
|
assert.strictEqual(replies[12][0], null);
|
|
assert.equal(replies[12][1], 'test');
|
|
assert.equal(replies[13][0], 'multibar2');
|
|
assert.equal(replies[13].length, 3);
|
|
assert.equal(replies.length, 14);
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('converts a non string key to a string', function (done) {
|
|
// TODO: Converting the key might change soon again.
|
|
client.multi().hmset(true, {
|
|
test: 123,
|
|
bar: 'baz'
|
|
}).exec(done);
|
|
});
|
|
|
|
it('runs a multi without any further commands', function (done) {
|
|
var buffering = client.multi().exec(function (err, res) {
|
|
assert.strictEqual(err, null);
|
|
assert.strictEqual(res.length, 0);
|
|
done();
|
|
});
|
|
assert(typeof buffering === 'boolean');
|
|
});
|
|
|
|
it('allows multiple operations to be performed using a chaining API', function (done) {
|
|
client.multi()
|
|
.mset('some', '10', 'keys', '20')
|
|
.incr('some')
|
|
.incr('keys')
|
|
.mget('some', ['keys'])
|
|
.exec(function (err, replies) {
|
|
assert.strictEqual(null, err);
|
|
assert.equal('OK', replies[0]);
|
|
assert.equal(11, replies[1]);
|
|
assert.equal(21, replies[2]);
|
|
assert.equal(11, replies[3][0].toString());
|
|
assert.equal(21, replies[3][1].toString());
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('allows multiple commands to work the same as normal to be performed using a chaining API', function (done) {
|
|
client.multi()
|
|
.mset(['some', '10', 'keys', '20'])
|
|
.incr('some', helper.isNumber(11))
|
|
.incr(['keys'], helper.isNumber(21))
|
|
.mget('some', 'keys')
|
|
.exec(function (err, replies) {
|
|
assert.strictEqual(null, err);
|
|
assert.equal('OK', replies[0]);
|
|
assert.equal(11, replies[1]);
|
|
assert.equal(21, replies[2]);
|
|
assert.equal(11, replies[3][0].toString());
|
|
assert.equal(21, replies[3][1].toString());
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', function () {
|
|
return client.multi()
|
|
.mset(['some', '10', 'keys', '20'])
|
|
.incr('some', helper.isNumber(11))
|
|
.incr(['keys'], helper.isNumber(21))
|
|
.mget('some', 'keys')
|
|
.execAsync()
|
|
.then(function (replies) {
|
|
assert.equal('OK', replies[0]);
|
|
assert.equal(11, replies[1]);
|
|
assert.equal(21, replies[2]);
|
|
assert.equal(11, replies[3][0].toString());
|
|
assert.equal(21, replies[3][1].toString());
|
|
});
|
|
});
|
|
|
|
it('allows an array to be provided indicating multiple operations to perform', function (done) {
|
|
// test nested multi-bulk replies with nulls.
|
|
client.multi([
|
|
['mget', ['multifoo', 'some', 'random value', 'keys']],
|
|
['incr', 'multifoo']
|
|
])
|
|
.exec(function (err, replies) {
|
|
assert.strictEqual(replies.length, 2);
|
|
assert.strictEqual(replies[0].length, 4);
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('allows multiple operations to be performed on a hash', function (done) {
|
|
client.multi()
|
|
.hmset('multihash', 'a', 'foo', 'b', 1)
|
|
.hmset('multihash', {
|
|
extra: 'fancy',
|
|
things: 'here'
|
|
})
|
|
.hgetall('multihash')
|
|
.exec(function (err, replies) {
|
|
assert.strictEqual(null, err);
|
|
assert.equal('OK', replies[0]);
|
|
assert.equal(Object.keys(replies[2]).length, 4);
|
|
assert.equal('foo', replies[2].a);
|
|
assert.equal('1', replies[2].b);
|
|
assert.equal('fancy', replies[2].extra);
|
|
assert.equal('here', replies[2].things);
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('reports EXECABORT exceptions when they occur (while queueing)', function (done) {
|
|
client.multi().config('bar').set('foo').set('bar').exec(function (err, reply) {
|
|
assert.equal(err.code, 'EXECABORT');
|
|
assert.equal(reply, undefined, 'The reply should have been discarded');
|
|
assert(err.message.match(/^EXECABORT/), 'Error message should begin with EXECABORT');
|
|
assert.equal(err.errors.length, 2, 'err.errors should have 2 items');
|
|
assert.strictEqual(err.errors[0].command, 'SET');
|
|
assert.strictEqual(err.errors[0].code, 'ERR');
|
|
assert.strictEqual(err.errors[0].position, 1);
|
|
assert(/^ERR/.test(err.errors[0].message), 'Actuall error message should begin with ERR');
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('reports multiple exceptions when they occur (while EXEC is running)', function (done) {
|
|
client.multi().config('bar').debug('foo').eval("return {err='this is an error'}", 0).exec(function (err, reply) {
|
|
assert.strictEqual(reply.length, 3);
|
|
assert.equal(reply[0].code, 'ERR');
|
|
assert.equal(reply[0].command, 'CONFIG');
|
|
assert.equal(reply[2].code, undefined);
|
|
assert.equal(reply[2].command, 'EVAL');
|
|
assert(/^this is an error/.test(reply[2].message));
|
|
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
|
|
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR');
|
|
return done();
|
|
});
|
|
});
|
|
|
|
it('reports multiple exceptions when they occur (while EXEC is running) promisified', function () {
|
|
return client.multi().config('bar').debug('foo').eval("return {err='this is an error'}", 0).execAsync().then(function (reply) {
|
|
assert.strictEqual(reply.length, 3);
|
|
assert.equal(reply[0].code, 'ERR');
|
|
assert.equal(reply[0].command, 'CONFIG');
|
|
assert.equal(reply[2].code, undefined);
|
|
assert.equal(reply[2].command, 'EVAL');
|
|
assert(/^this is an error/.test(reply[2].message));
|
|
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
|
|
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR');
|
|
});
|
|
});
|
|
|
|
it('reports multiple exceptions when they occur (while EXEC is running) and calls cb', function (done) {
|
|
var multi = client.multi();
|
|
multi.config('bar', helper.isError());
|
|
multi.set('foo', 'bar', helper.isString('OK'));
|
|
multi.debug('foo').exec(function (err, reply) {
|
|
assert.strictEqual(reply.length, 3);
|
|
assert.strictEqual(reply[0].code, 'ERR');
|
|
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR');
|
|
assert(/^ERR/.test(reply[2].message), 'Error message should begin with ERR');
|
|
assert.strictEqual(reply[1], 'OK');
|
|
client.get('foo', helper.isString('bar', done));
|
|
});
|
|
});
|
|
|
|
it('emits an error if no callback has been provided and execabort error occured', function (done) {
|
|
var multi = client.multi();
|
|
multi.config('bar');
|
|
multi.set('foo');
|
|
multi.exec();
|
|
|
|
client.on('error', function (err) {
|
|
assert.equal(err.code, 'EXECABORT');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should work without any callback', function (done) {
|
|
var multi = client.multi();
|
|
multi.set('baz', 'binary');
|
|
multi.set('foo', 'bar');
|
|
multi.exec();
|
|
|
|
client.get('foo', helper.isString('bar', done));
|
|
});
|
|
|
|
it('should not use a transaction with exec_atomic if no command is used', function () {
|
|
var multi = client.multi();
|
|
var test = false;
|
|
multi.exec_batch = function () {
|
|
test = true;
|
|
};
|
|
multi.exec_atomic();
|
|
assert(test);
|
|
});
|
|
|
|
it('should not use a transaction with exec_atomic if only one command is used', function () {
|
|
var multi = client.multi();
|
|
var test = false;
|
|
multi.exec_batch = function () {
|
|
test = true;
|
|
};
|
|
multi.set('baz', 'binary');
|
|
multi.EXEC_ATOMIC();
|
|
assert(test);
|
|
});
|
|
|
|
it('should use transaction with exec_atomic and more than one command used', function (done) {
|
|
var multi = client.multi();
|
|
var test = false;
|
|
multi.exec_batch = function () {
|
|
test = true;
|
|
};
|
|
multi.set('baz', 'binary');
|
|
multi.get('baz');
|
|
multi.exec_atomic(done);
|
|
assert(!test);
|
|
});
|
|
|
|
it('do not mutate arguments in the multi constructor', function (done) {
|
|
var input = [['set', 'foo', 'bar'], ['get', 'foo']];
|
|
client.multi(input).exec(function (err, res) {
|
|
assert.strictEqual(input.length, 2);
|
|
assert.strictEqual(input[0].length, 3);
|
|
assert.strictEqual(input[1].length, 2);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('works properly after a reconnect. issue #897', function (done) {
|
|
client.stream.destroy();
|
|
client.on('error', function (err) {
|
|
assert.strictEqual(err.code, 'ECONNREFUSED');
|
|
});
|
|
client.on('ready', function () {
|
|
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
|
|
assert(!err);
|
|
assert.strictEqual(res[1], 'bar');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('emits error once if reconnecting after multi has been executed but not yet returned without callback', function (done) {
|
|
// NOTE: If uncork is called async by postponing it to the next tick, this behavior is going to change.
|
|
// The command won't be processed anymore two errors are returned instead of one
|
|
client.on('error', function (err) {
|
|
assert.strictEqual(err.code, 'UNCERTAIN_STATE');
|
|
client.get('foo', function (err, res) {
|
|
assert.strictEqual(res, 'bar');
|
|
done();
|
|
});
|
|
});
|
|
|
|
// The commands should still be fired, no matter that the socket is destroyed on the same tick
|
|
client.multi().set('foo', 'bar').get('foo').exec();
|
|
// Abort connection before the value returned
|
|
client.stream.destroy();
|
|
});
|
|
|
|
it('indivdual commands work properly with multi', function (done) {
|
|
// Neither of the following work properly in a transactions:
|
|
// (This is due to Redis not returning the reply as expected / resulting in undefined behavior)
|
|
// (Likely there are more commands that do not work with a transaction)
|
|
//
|
|
// auth => can't be called after a multi command
|
|
// monitor => results in faulty return values e.g. multi().monitor().set('foo', 'bar').get('foo')
|
|
// returns ['OK, 'OK', 'monitor reply'] instead of ['OK', 'OK', 'bar']
|
|
// quit => ends the connection before the exec
|
|
// client reply skip|off => results in weird return values. Not sure what exactly happens
|
|
// subscribe => enters subscribe mode and this does not work in combination with exec (the same for psubscribe, unsubscribe...)
|
|
//
|
|
|
|
assert.strictEqual(client.selected_db, undefined);
|
|
var multi = client.multi();
|
|
multi.select(5, function (err, res) {
|
|
assert.strictEqual(client.selected_db, 5);
|
|
assert.strictEqual(res, 'OK');
|
|
assert.notDeepEqual(client.server_info.db5, { avg_ttl: 0, expires: 0, keys: 1 });
|
|
});
|
|
// multi.client('reply', 'on', helper.isString('OK')); // Redis v.3.2
|
|
multi.set('foo', 'bar', helper.isString('OK'));
|
|
multi.info(function (err, res) {
|
|
assert.strictEqual(res.indexOf('# Server\r\nredis_version:'), 0);
|
|
assert.deepEqual(client.server_info.db5, { avg_ttl: 0, expires: 0, keys: 1 });
|
|
});
|
|
multi.get('foo', helper.isString('bar'));
|
|
multi.exec(function (err, res) {
|
|
res[2] = res[2].substr(0, 10);
|
|
assert.deepEqual(res, ['OK', 'OK', '# Server\r\n', 'bar']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|