diff --git a/test/commands/batch.spec.js b/test/commands/batch.spec.js new file mode 100644 index 0000000000..0e009c5b21 --- /dev/null +++ b/test/commands/batch.spec.js @@ -0,0 +1,311 @@ +'use strict'; + +var assert = require('assert'); +var config = require("../lib/config"); +var helper = require('../helper'); +var redis = config.redis; +var uuid = require('uuid'); + +describe("The 'batch' method", function () { + + helper.allTests(function(parser, ip, args) { + + describe("using " + parser + " and " + ip, function () { + var key, value; + + beforeEach(function () { + key = uuid.v4(); + value = uuid.v4(); + }); + + describe("when not connected", function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("connect", function () { + client.quit(); + }); + client.on('end', function () { + return done(); + }); + }); + + it("returns an empty array", function (done) { + var batch = client.batch(); + batch.exec(function (err, res) { + assert.strictEqual(err, null); + assert.strictEqual(res.length, 0); + done(); + }); + }); + + it("returns an empty array if promisified", function () { + return client.batch().execAsync().then(function(res) { + assert.strictEqual(res.length, 0); + }); + }); + }); + + describe("when connected", function () { + var client; + + beforeEach(function (done) { + client = redis.createClient.apply(redis.createClient, args); + client.once("connect", function () { + client.flushdb(function (err) { + return done(err); + }); + }); + }); + + afterEach(function () { + client.end(); + }); + + it('fail individually when one command fails using chaining notation', function (done) { + var batch1, batch2; + batch1 = client.batch(); + batch1.mset("batchfoo", "10", "batchbar", "20", helper.isString("OK")); + + // Provoke an error at queue time + batch1.set("foo2", helper.isError()); + batch1.incr("batchfoo"); + batch1.incr("batchbar"); + batch1.exec(function () { + // Confirm that the previous command, while containing an error, still worked. + batch2 = client.batch(); + batch2.get('foo2', helper.isNull()); + batch2.incr("batchbar", helper.isNumber(22)); + batch2.incr("batchfoo", helper.isNumber(12)); + batch2.exec(function (err, replies) { + assert.strictEqual(null, replies[0]); + assert.strictEqual(22, replies[1]); + assert.strictEqual(12, replies[2]); + return done(); + }); + }); + }); + + it('fail individually when one command fails and emit the error if no callback has been provided', function (done) { + var batch1; + client.on('error', function (err) { + done(err); + }); + batch1 = client.batch(); + batch1.mset("batchfoo", "10", "batchbar", "20", helper.isString("OK")); + + // Provoke an error at queue time + batch1.set("foo2"); + batch1.incr("batchfoo"); + batch1.incr("batchbar"); + batch1.exec(function (err, res) { + assert.strictEqual(res[1].command, 'SET'); + assert.strictEqual(res[1].code, 'ERR'); + done(); + }); + }); + + it('fail individually when one command in an array of commands fails', function (done) { + // test nested batch-bulk replies + client.batch([ + ["mget", "batchfoo", "batchbar", function (err, res) { + assert.strictEqual(2, res.length); + assert.strictEqual(0, +res[0]); + assert.strictEqual(0, +res[1]); + }], + ["set", "foo2", helper.isError()], + ["incr", "batchfoo"], + ["incr", "batchbar"] + ]).exec(function (err, replies) { + assert.strictEqual(2, replies[0].length); + assert.strictEqual(null, replies[0][0]); + assert.strictEqual(null, replies[0][1]); + assert.strictEqual('SET', replies[1].command); + assert.strictEqual("1", replies[2].toString()); + assert.strictEqual("1", replies[3].toString()); + return done(); + }); + }); + + it('handles batchple 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 batch-bulk replies with empty mb elements. + client.BATCH([ + ["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 batchple operations to be performed using constructor with all kinds of syntax', function (done) { + var now = Date.now(); + var arr = ["batchhmset", "batchbar", "batchbaz"]; + var arr2 = ['some manner of key', 'otherTypes']; + var arr3 = [5768, "batchbarx", "batchfoox"]; + var arr4 = ["mset", [578, "batchbar"], helper.isString('OK')]; + client.batch([ + arr4, + [["mset", "batchfoo2", "batchbar2", "batchfoo3", "batchbar3"], helper.isString('OK')], + ["hmset", arr], + [["hmset", "batchhmset2", "batchbar2", "batchfoo3", "batchbar3", "test", helper.isString('OK')]], + ["hmset", ["batchhmset", "batchbar", "batchfoo", 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", "batchhmset", ["batchbar", "batchbaz"]], + ["hmset", "batchhmset", ["batchbar", "batchbaz"], helper.isString('OK')], + ]) + .hmget(now, 123456789, 'otherTypes') + .hmget('key2', arr2, function noop() {}) + .hmget(['batchhmset2', 'some manner of key', 'batchbar3']) + .mget('batchfoo2', ['batchfoo3', 'batchfoo'], function(err, res) { + assert(res[0], 'batchfoo3'); + assert(res[1], 'batchfoo'); + }) + .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], 'batchbar2'); + 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.batch().hmset(true, { + test: 123, + bar: 'baz' + }).exec(done); + }); + + it('runs a batch without any further commands', function(done) { + var buffering = client.batch().exec(function(err, res) { + assert.strictEqual(err, null); + assert.strictEqual(res.length, 0); + done(); + }); + assert(typeof buffering === 'boolean'); + }); + + it('runs a batch without any further commands and without callback', function() { + var buffering = client.batch().exec(); + assert(typeof buffering === 'boolean'); + assert(!buffering); + }); + + it('allows batchple operations to be performed using a chaining API', function (done) { + client.batch() + .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 batchple commands to work the same as normal to be performed using a chaining API', function (done) { + client.batch() + .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 batchple commands to work the same as normal to be performed using a chaining API promisified', function () { + return client.batch() + .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 batchple operations to perform', function (done) { + // test nested batch-bulk replies with nulls. + client.batch([ + ["mget", ["batchfoo", "some", "random value", "keys"]], + ["incr", "batchfoo"] + ]) + .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.batch() + .hmset("batchhash", "a", "foo", "b", 1) + .hmset("batchhash", { + extra: "fancy", + things: "here" + }) + .hgetall("batchhash") + .exec(done); + }); + + it("should work without any callback", function (done) { + helper.serverVersionAtLeast.call(this, client, [2, 6, 5]); + + var batch = client.batch(); + batch.set("baz", "binary"); + batch.set("foo", "bar"); + batch.exec(); + + client.get('foo', helper.isString('bar', done)); + }); + + }); + }); + }); +});