stipsan / ioredis-mock

Emulates ioredis by performing all operations in-memory.
MIT License
341 stars 124 forks source link

Feature: Redis Sentinels #1032

Open Abhi-Codes opened 3 years ago

Abhi-Codes commented 3 years ago

I am a newbie to Jest mocking , I came through this module and wanted to implement it but I am struggling . Hopefully someone can help me here . I have a helper file which provides me the connection object to ioredis sentinel cluster and I use this helper in another Express route handler file to fetch the values from redis. I am trying to test the route handler file where I need the ioredis mocking .

Any help to get me started is really appreciable .

Redis.js

const Redis = require("ioredis");
const config = require("../config");
const Logger = require("../logger");
const redisLogger = new Logger("redis");
var redis_sentinel_hosts = config.redis_sentinel_hosts;
var redis_sentinel_port = config.redis_sentinel_port;
var redis_sentinel_name = config.redis_sentinel_name;
var redis_sentinel_password = config.redis_sentinel_password;

//Frame the sentinels
var sentinels = [];
var toSplit = redis_sentinel_hosts.split(",");
for (var i = 0; i < toSplit.length; i++) {
  sentinels.push({ host: toSplit[i], port: redis_sentinel_port });
}

//Connect to Redis
const redis_client = new Redis({
  sentinels: sentinels,
  name: redis_sentinel_name,
  password: redis_sentinel_password,
  sentinelPassword: redis_sentinel_password,
  enableOfflineQueue: false, // when redis connection is in error , any command issued wont be put to a queue and error will be thrown
  maxRetriesPerRequest: 1,
  sentinelRetryStrategy: function (times) {
    return 2000; // reconnect after 2 seconds
  },
  role: "slave", // always connect to slave
});

redis_client.on("connect", function () {
  redisLogger.info("Redis client connected to slave");
});
redis_client.on("error", function (err) {
  redisLogger.info("Redis client Something went wrong ", err);
});

module.exports = redis_client;

Route Handler file .js

const express = require("express");
const { createProxyMiddleware } = require("http-proxy-middleware");
const config = require("../config");
const client = require("../helpers/init_redis.js");
const Logger = require("../logger");
const verifyToken = require("../middleware/verifyToken");
const Redis = require("ioredis");
var PHPUnserialize = require("php-unserialize");

const drupal_host_url = config.drupal_host_url;
const cacheKeyPrefix = config.cache_key_prefix;
const drupalLogger = new Logger("drupal");
const router = express.Router();

// Function to retrieve hash set from cache
function cache(req, res, next) {
  const url = req.url;
  var pos = url.indexOf("?");
  var type = req.private == true ? "?type=private" : "?type=public";
  var cachekey = cacheKeyPrefix.concat(url.slice(4, pos)).concat(type);
  drupalLogger.info("<<<< Cache Key >>>>", cachekey);
  client.hgetall(cachekey, (err, data) => {
    if (err) {
      if (err instanceof Redis.ReplyError) {
        drupalLogger.info("<<<< Redis Reply Error >>>>", err.message);
        res.status(500).json({
          error: err.name,
          error_description: err.message,
        });
        return;
      } else {
        drupalLogger.info("<<<< Redis Connection Error >>>>", err.message);
        req.redisErr = true;
        return next(err);
      }
    }
    if (Object.keys(data).length !== 0 && data !== null) {
      drupalLogger.info("<<<< Found in Cache >>>>", typeof data);
      res.send(PHPUnserialize.unserialize(data.data));
    } else {
      drupalLogger.info("<<<< Not Found in Cache >>>>");
      next();
    }
  });
}

function customPathRewrite() {
  return (path, req) => {
    if (req.private == true) {
      path.replace("/drupal", "").concat("&type=private");
      return path;
    } else {
      path = path.replace("/drupal", "").concat("&type=public");
      return path;
    }
  };
}

function onProxyRes(proxyResponse, request, response) {
  delete proxyResponse.headers["x-powered-by"];
  delete proxyResponse.headers["x-generator"];
  delete proxyResponse.headers["server"];
}

router.get(
  "*",
  verifyToken,
  cache,
  createProxyMiddleware({
    target: drupal_host_url,
    changeOrigin: true,
    onProxyRes: onProxyRes,
    pathRewrite: customPathRewrite(),
  })
);

router.use(
  function (err, req, res, next) {
    drupalLogger.info("<<<< Inside Error Middleware >>>>");
    err.statusCode = err.statusCode || 500;
    err.name = err.name || "Error";
    err.message = err.message || "Something went wrong";
    if (req.redisErr && req.redisErr == true) {
      next();
    } else {
      res.status(err.statusCode).json({
        error: err.name,
        error_description: err.message,
      });
    }
    return;
  },
  createProxyMiddleware({
    target: drupal_host_url,
    changeOrigin: true,
    onProxyRes: onProxyRes,
    pathRewrite: customPathRewrite(),
  })
);
module.exports = router;

Mock_redis.js

const ioredisMock = require('ioredis-mock')
var redis = new ioredisMock({
  // `options.data` does not exist in `ioredis`, only `ioredis-mock`
  data: {
    'wellbeing_:default:v1/api/article/1&type=public': { id: '1', username: 'superman', email: 'clark@daily.planet' },
    'wellbeing_:default:v1/api/article/2&type=public': { id: '1', username: 'superman', email: 'clark@daily.planet' },
    'wellbeing_:default:v1/api/article/3&type=public': { id: '1', username: 'superman', email: 'clark@daily.planet' },
    'wellbeing_:default:v1/api/article/4&type=public': { id: '1', username: 'superman', email: 'clark@daily.planet' },
    'user:2': { id: '2', username: 'batman', email: 'bruce@wayne.enterprises' },
  },
});
jest.setMock('ioredis', redis)
stipsan commented 3 years ago

Hey @Abhi-Codes the reason it's not working is because ioredis-mock doesn't support Sentinels yet. This PR needs to land before we can start working on that: #997