Skip to content

Commit ac7dcff

Browse files
committed
memory storage finished + unit tests
1 parent 53bbb19 commit ac7dcff

File tree

3 files changed

+1635
-111
lines changed

3 files changed

+1635
-111
lines changed

src/MemoryStorage.js

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@ var
55
getKeys = {getter: true, required: ['keys']},
66
getMember = {getter: true, required: ['_id', 'member']},
77
getxScan = {getter: true, required: ['_id', 'cursor'], opts: ['match', 'count']},
8-
getZrange = {getter: true, required: ['_id', 'min', 'max'], opts: ['limit', 'options']},
8+
getZrange = {
9+
getter: true,
10+
required: ['_id', 'start', 'stop'],
11+
opts: assignZrangeOptions,
12+
mapResults: mapZrangeResults
13+
},
14+
getZrangeBy = {
15+
getter: true,
16+
required: ['_id', 'min', 'max'],
17+
opts: assignZrangeOptions,
18+
mapResults: mapZrangeResults
19+
},
920
setId = {required: ['_id']},
1021
setIdValue = {required: ['_id', 'value']},
1122
setIdFieldValue = {required: ['_id', 'field', 'value']},
12-
setIdEntries = {required: ['_id', 'entries']};
23+
setEntries = {required: ['entries']};
1324

1425
// Redis commands
1526
var
@@ -54,13 +65,13 @@ var
5465
hdel: {required: ['_id', 'fields']},
5566
hexists: getIdField,
5667
hget: getIdField,
57-
hgetall: getId,
68+
hgetall: {getter: true, required: ['_id'], mapResults: mapKeyValueResults},
5869
hincrby: setIdFieldValue,
5970
hincrbyfloat: {required: ['_id', 'field', 'value'], mapResults: parseFloat},
6071
hkeys: getId,
6172
hlen: getId,
6273
hmget: {getter: true, required: ['_id', 'fields']},
63-
hmset: setIdEntries,
74+
hmset: {required: ['_id', 'entries']},
6475
hscan: getxScan,
6576
hset: setIdFieldValue,
6677
hsetnx: setIdFieldValue,
@@ -69,7 +80,7 @@ var
6980
incr: setId,
7081
incrby: setIdValue,
7182
incrbyfloat: {required: ['_id', 'value'], mapResults: parseFloat},
72-
keys: {getter: true, required: ['_id', 'pattern']},
83+
keys: {getter: true, required: ['pattern']},
7384
lindex: {getter: true, required: ['_id', 'index']},
7485
linsert: {required: ['_id', 'position', 'pivot', 'value']},
7586
llen: getId,
@@ -81,8 +92,8 @@ var
8192
lset: {required: ['_id', 'index', 'value']},
8293
ltrim: {required: ['_id', 'start', 'stop']},
8394
mget: getKeys,
84-
mset: setIdEntries,
85-
msetnx: setIdEntries,
95+
mset: setEntries,
96+
msetnx: setEntries,
8697
object: {getter: true, required: ['_id', 'subcommand']},
8798
persist: setId,
8899
pexpire: {required: ['_id', 'milliseconds']},
@@ -103,7 +114,7 @@ var
103114
sadd: {required: ['_id', 'members']},
104115
scan: {getter: true, required: ['cursor'], opts: ['match', 'count']},
105116
scard: getId,
106-
sdiff: getKeys,
117+
sdiff: {getter: true, required: ['_id', 'keys']},
107118
sdiffstore: {required: ['_id', 'keys', 'destination']},
108119
set: {required: ['_id', 'value'], opts: ['ex', 'px', 'nx', 'xx']},
109120
setex: {required: ['_id', 'value', 'seconds']},
@@ -113,15 +124,15 @@ var
113124
sismember: getMember,
114125
smembers: getId,
115126
smove: {required: ['_id', 'destination', 'member']},
116-
sort: {required: ['_id'], opts: ['alpha', 'by', 'direction', 'get', 'limit']},
127+
sort: {getter: true, required: ['_id'], opts: ['alpha', 'by', 'direction', 'get', 'limit']},
117128
spop: {required: ['_id'], opts: ['count'], mapResults: mapStringToArray },
118-
srandmember: {getter: true, required: ['_id'], opts: ['count']},
129+
srandmember: {getter: true, required: ['_id'], opts: ['count'], mapResults: mapStringToArray},
119130
srem: {required: ['_id', 'members']},
120131
sscan: getxScan,
121132
strlen: getId,
122133
sunion: getKeys,
123-
sunionstore: {required: ['keys', 'destination']},
124-
time: {getter: true},
134+
sunionstore: {required: ['destination', 'keys']},
135+
time: {getter: true, mapResults: mapArrayStringToArrayInt},
125136
touch: {required: ['keys']},
126137
ttl: getId,
127138
type: getId,
@@ -131,17 +142,17 @@ var
131142
zincrby: {required: ['_id', 'member', 'value']},
132143
zinterstore: {required: ['_id', 'keys'], opts: ['weights', 'aggregate']},
133144
zlexcount: {getter: true, required: ['_id', 'min', 'max']},
134-
zrange: {getter: true, required: ['_id', 'start', 'stop'], opts: ['options']},
145+
zrange: getZrange,
135146
zrangebylex: {getter: true, required: ['_id', 'min', 'max'], opts: ['limit']},
136147
zrevrangebylex: {getter: true, required: ['_id', 'min', 'max'], opts: ['limit']},
137-
zrangebyscore: getZrange,
148+
zrangebyscore: getZrangeBy,
138149
zrank: getMember,
139150
zrem: {required: ['_id', 'members']},
140151
zremrangebylex: {required: ['_id', 'min', 'max']},
141152
zremrangebyrank: {required: ['_id', 'start', 'stop']},
142153
zremrangebyscore: {required: ['_id', 'min', 'max']},
143-
zrevrange: {getter: true, required: ['_id', 'start', 'stop'], opts: ['options']},
144-
zrevrangebyscore: getZrange,
154+
zrevrange: getZrange,
155+
zrevrangebyscore: getZrangeBy,
145156
zrevrank: getMember,
146157
zscan: getxScan,
147158
zscore: getMember,
@@ -221,9 +232,10 @@ function MemoryStorage(kuzzle) {
221232

222233
if (args.length && typeof args[args.length - 1] === 'function') {
223234
cb = args.pop();
224-
commands[command].getter && this.kuzzle.callbackRequired('MemoryStorage.' + command, cb);
225235
}
226236

237+
commands[command].getter && this.kuzzle.callbackRequired('MemoryStorage.' + command, cb);
238+
227239
if (!commands[command].getter) {
228240
data.body = {};
229241
}
@@ -248,13 +260,10 @@ function MemoryStorage(kuzzle) {
248260
throw new Error('MemoryStorage.' + command + ': Invalid optional parameter (expected an object)');
249261
}
250262

251-
options = args[0];
263+
if (args.length) {
264+
options = Object.assign({}, args[0]);
252265

253-
if (args.length && commands[command].opts) {
254-
if (typeof commands[command].opts === 'function') {
255-
commands[command].opts(data, options);
256-
}
257-
else {
266+
if (Array.isArray(commands[command].opts)) {
258267
commands[command].opts.forEach(function (opt) {
259268
if (options[opt] !== null && options[opt] !== undefined) {
260269
assignParameter(data, commands[command].getter, opt, options[opt]);
@@ -264,6 +273,14 @@ function MemoryStorage(kuzzle) {
264273
}
265274
}
266275

276+
/*
277+
Options function mapper does not necessarily need
278+
options to be passed by clients.
279+
*/
280+
if (typeof commands[command].opts === 'function') {
281+
commands[command].opts(data, options || {});
282+
}
283+
267284
this.kuzzle.query(query, data, options, cb && function (err, res) {
268285
if (err) {
269286
return cb(err);
@@ -303,7 +320,7 @@ function assignParameter(data, getter, name, value) {
303320
* Assign the provided options for the georadius* redis functions
304321
* to the request object, as expected by Kuzzle API
305322
*
306-
* Mutates the provided options object
323+
* Mutates the provided data and options objects
307324
*
308325
* @param {object} data
309326
* @param {object} options
@@ -336,6 +353,23 @@ function assignGeoRadiusOptions(data, options) {
336353
}
337354
}
338355

356+
/**
357+
* Force the WITHSCORES option on z*range* routes
358+
*
359+
* Mutates the provided data and options objects
360+
*
361+
* @param {object} data
362+
* @param {object} options
363+
*/
364+
function assignZrangeOptions(data, options) {
365+
data.options = ['withscores'];
366+
367+
if (options.limit) {
368+
data.limit = options.limit;
369+
delete options.limit;
370+
}
371+
}
372+
339373
/**
340374
* Maps geopos results, from array<array<string>> to array<array<number>>
341375
*
@@ -407,4 +441,78 @@ function mapStringToArray (results) {
407441
return Array.isArray(results) ? results : [results];
408442
}
409443

444+
/**
445+
* Map an array of strings to an array of integers
446+
*
447+
* @param {Array.<string>} results
448+
* @return {Array.<Number>}
449+
*/
450+
function mapArrayStringToArrayInt(results) {
451+
return results.map(function (value) {
452+
return parseInt(value);
453+
});
454+
}
455+
456+
/**
457+
* Map results like ['key', 'value', 'key', 'value', ...]
458+
* to a JSON object {key: 'value', ...}
459+
*
460+
* @param {Array.<string>} results
461+
* @return {Object}
462+
*/
463+
function mapKeyValueResults(results) {
464+
var
465+
buffer = null,
466+
mapped = {};
467+
468+
results.forEach(function (value) {
469+
if (buffer === null) {
470+
buffer = value;
471+
}
472+
else {
473+
mapped[buffer] = value;
474+
buffer = null;
475+
}
476+
});
477+
478+
return mapped;
479+
}
480+
481+
/**
482+
* Map zrange results with WITHSCORES:
483+
* [
484+
* "member1",
485+
* "score of member1",
486+
* "member2",
487+
* "score of member2"
488+
* ]
489+
*
490+
* into the following format:
491+
* [
492+
* {"member": "member1", "score": <score of member1>},
493+
* {"member": "member2", "score": <score of member2>},
494+
* ]
495+
*
496+
*
497+
* @param {Array.<string>} results
498+
* @return {Array.<Object>}
499+
*/
500+
function mapZrangeResults(results) {
501+
var
502+
buffer = null,
503+
mapped = [];
504+
505+
results.forEach(function (value) {
506+
if (buffer === null) {
507+
buffer = value;
508+
}
509+
else {
510+
mapped.push({member: buffer, score: parseFloat(value)});
511+
buffer = null;
512+
}
513+
});
514+
515+
return mapped;
516+
}
517+
410518
module.exports = MemoryStorage;

test/MemoryStorage/constructor.test.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('MemoryStorage constructor', function () {
3939
return (typeof ms[p] === 'function' && ['constructor', 'setHeaders'].indexOf(p) === -1);
4040
});
4141

42-
should(functions.length).be.eql(119);
42+
should(functions.length).be.eql(117);
4343

4444
functions.forEach(function (f) {
4545
should(ms[f + 'Promise']).be.a.Function();
@@ -60,4 +60,14 @@ describe('MemoryStorage constructor', function () {
6060
should(ms.headers).match({foo: 'bar', bar: 'foobar'});
6161
});
6262

63+
it('auto-generated functions should throw if the wrong number of parameters is provided', function () {
64+
var
65+
emptyFunc = function () {},
66+
kuzzle = new Kuzzle('foo'),
67+
ms = new MemoryStorage(kuzzle);
68+
69+
should(function () {ms.dbsize('foo', {}, emptyFunc);}).throw('MemoryStorage.dbsize: Too many parameters provided');
70+
should(function () {ms.mget(emptyFunc);}).throw('MemoryStorage.mget: Missing parameter "keys"');
71+
should(function () {ms.ping(['foo', 'bar'], emptyFunc);}).throw('MemoryStorage.ping: Invalid optional parameter (expected an object)');
72+
});
6373
});

0 commit comments

Comments
 (0)