Skip to content

Commit

Permalink
fix: Fix transformers
Browse files Browse the repository at this point in the history
The transformers implementation was never compatible with ioredis, this
fixes it and also adds much-needed tests.

Fixes: stipsan#1051
  • Loading branch information
silverwind committed Dec 13, 2021
1 parent 04fcbca commit 7636016
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 21 deletions.
27 changes: 20 additions & 7 deletions src/command.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';
import asCallback from 'standard-as-callback';
import { Command as IoredisCommand } from 'ioredis';
import promiseContainer from './promise-container';

export function isInSubscriberMode(RedisMock) {
Expand Down Expand Up @@ -52,6 +53,18 @@ export function throwIfCommandIsNotAllowed(commandName, RedisMock) {
throwIfNotConnected(commandName, RedisMock);
}

export const Command = {
// eslint-disable-next-line no-underscore-dangle
transformers: IoredisCommand._transformer,
setArgumentTransformer: (name, func) => {
Command.transformers.argument[name] = func;
},

setReplyTransformer: (name, func) => {
Command.transformers.reply[name] = func;
},
};

/**
* Transform non-buffer arguments to strings to simulate real ioredis behavior
* @param {any} arg the argument to transform
Expand All @@ -61,21 +74,21 @@ const argMapper = (arg) => {
return arg instanceof Buffer ? arg : arg.toString();
};

export function processArguments(args, commandName, RedisMock) {
export function processArguments(args, commandName) {
// fast return, the defineCommand command requires NO transformation of args
if (commandName === 'defineCommand') return args;

let commandArgs = args ? _.flatten(args) : [];
if (RedisMock.Command.transformers.argument[commandName]) {
commandArgs = RedisMock.Command.transformers.argument[commandName](args);
if (Command.transformers.argument[commandName]) {
commandArgs = Command.transformers.argument[commandName](args);
}
commandArgs = commandArgs.map(argMapper);
return commandArgs;
}

export function processReply(result, commandName, RedisMock) {
if (RedisMock.Command.transformers.reply[commandName]) {
return RedisMock.Command.transformers.reply[commandName](result);
export function processReply(result, commandName) {
if (Command.transformers.reply[commandName]) {
return Command.transformers.reply[commandName](result);
}
return result;
}
Expand All @@ -88,7 +101,7 @@ export function safelyExecuteCommand(
) {
throwIfCommandIsNotAllowed(commandName, RedisMock);
const result = commandEmulator(...commandArgs);
return processReply(result, commandName, RedisMock);
return processReply(result, commandName);
}

export default function command(commandEmulator, commandName, RedisMock) {
Expand Down
16 changes: 3 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* eslint-disable max-classes-per-file */
import { EventEmitter } from 'events';
import { Command } from 'ioredis';
import redisCommands from 'redis-commands';
import * as commands from './commands';
import * as commandsStream from './commands-stream';
import createCommand from './command';
import createCommand, { Command } from './command';
import createData from './data';
import createExpires from './expires';
import emitConnectEvent from './commands-utils/emitConnectEvent';
Expand Down Expand Up @@ -163,17 +162,8 @@ class RedisMock extends EventEmitter {
});
}
}
RedisMock.prototype.Command = {
// eslint-disable-next-line no-underscore-dangle
transformers: Command._transformer,
setArgumentTransformer: (name, func) => {
RedisMock.prototype.Command.transformers.argument[name] = func;
},

setReplyTransformer: (name, func) => {
RedisMock.prototype.Command.transformers.reply[name] = func;
},
};

RedisMock.Command = Command;

RedisMock.Cluster = class RedisClusterMock extends RedisMock {
constructor(nodesOptions) {
Expand Down
2 changes: 1 addition & 1 deletion src/pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Pipeline {
args.length = lastArgIndex;
}
const commandEmulator = command.bind(this.redis);
const commandArgs = processArguments(args, commandName, this.redis);
const commandArgs = processArguments(args, commandName);

this._addTransaction(commandEmulator, commandName, commandArgs, callback);
return this;
Expand Down
53 changes: 53 additions & 0 deletions test/command.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Redis from 'ioredis';
import command from '../src/command';

describe('basic command', () => {
Expand Down Expand Up @@ -30,3 +31,55 @@ describe('basic command', () => {
'should reject the promise if the first argument is bool false to allow simulating failures'
);
});

describe('transformers', () => {
it('should support setReplyTransformer', async () => {
const redis = new Redis();
Redis.Command.setReplyTransformer('hgetall', result => Object.entries(result));
await redis.hset('replytest', 'bar', 'baz');
await redis.hset('replytest', 'baz', 'quz');
expect(await redis.hgetall('replytest')).toEqual([['bar', 'baz'], ['baz', 'quz']]);
delete Redis.Command.transformers.reply.hgetall;
});

it('should support setArgumentTransformer', async () => {
const redis = new Redis();

function convertMapToArray(map) {
const result = [];
let pos = 0;
map.forEach((value, key) => {
result[pos] = key;
result[pos + 1] = value;
pos += 2;
});
return result;
}

function convertObjectToArray(obj) {
const result = [];
const keys = Object.keys(obj);

for (let i = 0, l = keys.length; i < l; i++) {
result.push(keys[i], obj[keys[i]]);
}
return result;
}

Redis.Command.setArgumentTransformer('hmset', args => {
if (args.length === 2) {
if (typeof Map !== 'undefined' && args[1] instanceof Map) {
return [args[0]].concat(convertMapToArray(args[1]))
}
if (typeof args[1] === 'object' && args[1] !== null) {
return [args[0]].concat(convertObjectToArray(args[1]))
}
}
return args;
})

await redis.hmset('argtest', { k1: 'v1', k2: 'v2' });
expect(await redis.hgetall('argtest')).toEqual({ k1: 'v1', k2: 'v2' });
delete Redis.Command.transformers.argument.hmset;
})
});

0 comments on commit 7636016

Please sign in to comment.