diff --git a/lib/keys.js b/lib/keys.js index f6cff1d..9ed0faf 100644 --- a/lib/keys.js +++ b/lib/keys.js @@ -93,52 +93,11 @@ exports.ttl = function (mockInstance, key, callback) { mockInstance._callCallback(callback, null, result); }; -/* Converting pattern into regex */ -function patternToRegex(pattern) { - - function process_plain(start, length) { - var plain = pattern.substr(start, length); - plain = plain.replace(/(\(|\)|\\|\.|\^|\$|\||\+)/gi - , function (spec) { - return '\\' + spec - }); - plain = plain.replace('*', '.*'); - plain = plain.replace('?', '.'); - return plain; - } - - var current_position = 0; - var parts = []; - var group_regex = /\[([^\]]+?)\]/ig; - - var matches; - while (matches = group_regex.exec(pattern)) { - if (matches.index > 0) { - parts.push(process_plain(current_position, matches.index - current_position)); - } - var groups = matches[1].split(''); - for (var i in groups) { - groups[i] = groups[i].replace(/(\(|\)|\\|\.|\^|\$|\||\?|\+|\*)/gi - , function (spec) { - return '\\' + spec - }); - } - - var group = '(' + groups.join('|') + ')' - parts.push(group); - current_position = matches.index + matches[0].length; - } - if (current_position != pattern.length) { - parts.push(process_plain(current_position, pattern.length - current_position)); - } - return new RegExp(parts.join('')); -} - /** * Keys */ exports.keys = function (mockInstance, pattern, callback) { - var regex = patternToRegex(pattern); + var regex = mockInstance._patternToRegex(pattern); var keys = []; for (var key in mockInstance.storage) { diff --git a/lib/redis-mock.js b/lib/redis-mock.js index 4c02bd5..dd04fb3 100755 --- a/lib/redis-mock.js +++ b/lib/redis-mock.js @@ -29,6 +29,46 @@ function RedisMock() { }); } }; + /* Converting pattern into regex */ + this._patternToRegex = function (pattern) { + + function process_plain(start, length) { + var plain = pattern.substr(start, length); + plain = plain.replace(/(\(|\)|\\|\.|\^|\$|\||\+)/gi + , function (spec) { + return '\\' + spec + }); + plain = plain.replace('*', '.*'); + plain = plain.replace('?', '.'); + return plain; + } + + var current_position = 0; + var parts = []; + var group_regex = /\[([^\]]+?)\]/ig; + + var matches; + while (matches = group_regex.exec(pattern)) { + if (matches.index > 0) { + parts.push(process_plain(current_position, matches.index - current_position)); + } + var groups = matches[1].split(''); + for (var i in groups) { + groups[i] = groups[i].replace(/(\(|\)|\\|\.|\^|\$|\||\?|\+|\*)/gi + , function (spec) { + return '\\' + spec + }); + } + + var group = '(' + groups.join('|') + ')' + parts.push(group); + current_position = matches.index + matches[0].length; + } + if (current_position != pattern.length) { + parts.push(process_plain(current_position, pattern.length - current_position)); + } + return new RegExp(parts.join('')); + }; } /** @@ -412,6 +452,10 @@ RedisClient.prototype.flushall = RedisClient.prototype.FLUSHALL = function (call serverfunctions.flushall.call(this, MockInstance, callback); } +RedisClient.prototype.scan = RedisClient.prototype.SCAN = function (args, callback) { + + serverfunctions.scan.call(this, MockInstance, args, callback); +} RedisMock.prototype.createClient = function (port_arg, host_arg, options) { diff --git a/lib/server.js b/lib/server.js index 5aa0550..8a1ca8b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -18,3 +18,46 @@ exports.flushdb = flushdb = function (mockInstance, callback) { * Exact the same as flushdb because multiple db is not supported yet */ exports.flushall = flushdb; + +/** + * scan + */ +// TODO: Support expanded arguments +exports.scan = scan = function (mockInstance, args, callback) { + var cursor = args[0]; + args = args.slice(1); + + /* Simulate random max returned elements (10 - 35 keys) */ + var count = Math.floor(Math.random() * 25 + cursor + 10) + var pattern = '*' + + /* Parse arguments array */ + for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (typeof arg === 'string') arg = arg.toLowerCase(); + if (i + 1 < args.length) { + if (arg === 'count') { + count = Number(args[i + 1]); + i++; + } + if (arg === 'match') { + pattern = args[i + 1]; + i++; + } + } + } + var regex = mockInstance._patternToRegex(pattern); + var keys = []; + + for (var key in mockInstance.storage) { + if (regex.test(key)) { + keys.push(key); + } + } + + var matched = keys.slice(cursor, count + cursor); + var newCursor = Math.min(count, matched.length); + /* Give back 0 for cursor if all keys are included */ + if (count + cursor >= keys.length) newCursor = 0; + return mockInstance._callCallback(callback, null, [newCursor, matched]); +} diff --git a/test/redis-mock.server.test.js b/test/redis-mock.server.test.js index cd9253a..1673aaa 100644 --- a/test/redis-mock.server.test.js +++ b/test/redis-mock.server.test.js @@ -32,3 +32,124 @@ describe("flushdb", function () { }); +describe("scan", function () { + + it("should return cursor and keys", function (done) { + + var r = redismock.createClient(); + + r.set("foo", "bar", function (err, result) { + + r.scan([0], function (err, result) { + + should(result instanceof Array).be.exactly(true); + should(result.length).be.exactly(2); + should(isNaN(result[0])).be.exactly(false); + should(result[0]).be.exactly(0); + should(result[1] instanceof Array).be.exactly(true); + should(result[1].length).be.exactly(1); + + r.end(); + done(); + + }); + + }); + + }); + + it("should work with patterns", function (done) { + + var r = redismock.createClient(); + + r.set("foo", "a", function (err, result) { + + r.set("family", "b", function (err, result) { + + r.set("burger", "food", function (err, result) { + + r.scan([0, 'MATCH', 'f*'], function (err, result) { + + should(result instanceof Array).be.exactly(true); + should(result.length).be.exactly(2); + should(isNaN(result[0])).be.exactly(false); + should(result[1] instanceof Array).be.exactly(true); + should(result[1].length).be.exactly(2); + + r.end(); + done(); + + }); + + }); + + }); + + }); + + }); + + it("should work with count", function (done) { + + var r = redismock.createClient(); + + r.set("foo", "a", function (err, result) { + + r.set("family", "b", function (err, result) { + + r.set("burger", "food", function (err, result) { + + r.scan([0, 'COUNT', 1], function (err, result) { + + should(result instanceof Array).be.exactly(true); + should(result.length).be.exactly(2); + should(isNaN(result[0])).be.exactly(false); + should(result[1] instanceof Array).be.exactly(true); + should(result[1].length).be.exactly(1); + + r.end(); + done(); + + }); + + }); + + }); + + }); + + }); + + it("should work with count, cursor, and patterns", function (done) { + + var r = redismock.createClient(); + + r.set("foo", "a", function (err, result) { + + r.set("family", "b", function (err, result) { + + r.set("burger", "food", function (err, result) { + + r.scan([1, 'COUNT', 1, 'MATCH', 'f*'], function (err, result) { + + should(result instanceof Array).be.exactly(true); + should(result.length).be.exactly(2); + should(isNaN(result[0])).be.exactly(false); + should(result[1] instanceof Array).be.exactly(true); + should(result[1].length).be.exactly(1); + should(result[1][0]).be.exactly('family'); + + r.end(); + done(); + + }); + + }); + + }); + + }); + + }); + +});