stipsan / ioredis-mock

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

Bug: SCAN commands have results in lua script indexed at 0, missing first element when iterating #1345

Open moritzraho opened 3 months ago

moritzraho commented 3 months ago

Reproduce

From the REPL (> node)

Redis = require('ioredis-mock'); redis = new Redis()
redis.defineCommand('scanWrapper', { lua: `local res = redis.call('SCAN', '0') for idx, member in ipairs(res[2]) do print(idx, member) end return res[2]` })
await redis.mset('a', '1', 'b', '2'); await redis.scanWrapper()

prints

1       b
at index 0 (wrong)      a

instead of expected

1        a
2       b
at index 0 (wrong)      nil

and returns

['b']

instead of

['a', 'b']

Patch (dirty)

for bundled v8.9.0 (with npx patch-package ioredis-mock)

diff --git a/node_modules/ioredis-mock/lib/index.js b/node_modules/ioredis-mock/lib/index.js
index a82c297..ac6fd63 100644
--- a/node_modules/ioredis-mock/lib/index.js
+++ b/node_modules/ioredis-mock/lib/index.js
@@ -3748,6 +3748,16 @@ var import_fengari = __toESM(require("fengari")), import_fengari_interop = __toE
 }, makeReturnValue = (L) => {
   if (!isTopArray(L)()) {
     let retVal2 = import_fengari_interop.default.tojs(L, -1);
+    // hack to support *scan commands
+    if (lua.lua_istable(L, -1)) {
+      lua.lua_len(L, -1);
+      let length = lua.lua_tointeger(L, -1);
+      retValArray = []
+      for (let i = 1; i <= length; i++) {
+        retValArray.push(retVal2.get(i))
+      }
+      return retValArray
+    }
     return Array.isArray(retVal2) ? retVal2.slice(1) : retVal2;
   }
   let arrayLength = getTopLength(L), table = import_fengari_interop.default.tojs(L, -1), retVal = [];
@@ -3830,9 +3840,15 @@ var { lua: lua2, to_luastring: toLuaString2 } = import_fengari2.default, defineR
       throw err;
     return import_fengari_interop2.default.push(vm.L, ["error", err.toString()]), 1;
   }
-  return result || result === 0 ? (Array.isArray(result) && (result.unshift(null), result[Symbol.for("__len")] = function() {
+  return result || result === 0 ? (
+    Array.isArray(result) && (result.unshift(null), result[Symbol.for("__len")] = function() {
+    return this.length - 1;
+  }),
+  Array.isArray(result[2]) && (result[2].unshift(null), result[2][Symbol.for("__len")] = function() {
     return this.length - 1;
-  }), import_fengari_interop2.default.push(vm.L, result), 1) : 0;
+  })
+  ,import_fengari_interop2.default.push(vm.L, result), 1
+) : 0;
 };
 function defineKeys(vm, numberOfKeys, commandArgs) {
   let keys2 = commandArgs.slice(0, numberOfKeys);