Repository: fppt/jedis-mock (java)

Issue:
Unsupported operation 'hlen' 
**Version: 0.1.15**

Sep 27, 2019 6:23:01 PM com.github.fppt.jedismock.server.RedisOperationExecutor execCommand
SEVERE: Malformed request
java.lang.UnsupportedOperationException: Unsupported operation 'hlen'
	at com.github.fppt.jedismock.operations.OperationFactory.buildTxOperation(OperationFactory.java:89)
	at com.github.fppt.jedismock.server.RedisOperationExecutor.execCommand(RedisOperationExecutor.java:39)
	at com.github.fppt.jedismock.server.RedisClient.run(RedisClient.java:54)
	at java.lang.Thread.run(Thread.java:748)

Pull request description:
Adding hlen operation.
Returns the number of fields contained in the hash stored at the specified key.

Closes #61 

Patch:
diff --git a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
index 64d17e3..c7bc7da 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
@@ -68,6 +68,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("hset", RO_hset::new);
         TRANSACTIONAL_OPERATIONS.put("hdel", RO_hdel::new);
         TRANSACTIONAL_OPERATIONS.put("hkeys", RO_hkeys::new);
+        TRANSACTIONAL_OPERATIONS.put("hlen", RO_hlen::new);
         TRANSACTIONAL_OPERATIONS.put("hgetall", RO_hgetall::new);
         TRANSACTIONAL_OPERATIONS.put("sinter", RO_sinter::new);
         TRANSACTIONAL_OPERATIONS.put("hmget", RO_hmget::new);
@@ -80,6 +81,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("zadd", RO_zadd::new);
         TRANSACTIONAL_OPERATIONS.put("zcard", RO_zcard::new);
         TRANSACTIONAL_OPERATIONS.put("zrange", RO_zrange::new);
+        TRANSACTIONAL_OPERATIONS.put("zrangebylex", RO_zrangebylex::new);
         TRANSACTIONAL_OPERATIONS.put("zrem", RO_zrem::new);
     }
 
@@ -104,6 +106,8 @@ public class OperationFactory {
                 return Optional.of(new RO_unsubscribe(state, params));
             case "quit":
                 return Optional.of(new RO_quit(state));
+            case "auth":
+                return Optional.of(new RO_auth(state));
             case "exec":
                 return Optional.of(new RO_exec(state));
             default:
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_auth.java b/src/main/java/com/github/fppt/jedismock/operations/RO_auth.java
new file mode 100644
index 0000000..75d4b92
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_auth.java
@@ -0,0 +1,19 @@
+package com.github.fppt.jedismock.operations;
+
+import com.github.fppt.jedismock.server.Response;
+import com.github.fppt.jedismock.server.Slice;
+import com.github.fppt.jedismock.storage.OperationExecutorState;
+
+public class RO_auth implements RedisOperation {
+    private OperationExecutorState state;
+
+    public RO_auth(OperationExecutorState state) {
+        this.state = state;
+    }
+
+    @Override
+    public Slice execute() {
+        state.owner().sendResponse(Response.clientResponse("auth", Response.OK), "auth");
+        return Response.SKIP;
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_hlen.java b/src/main/java/com/github/fppt/jedismock/operations/RO_hlen.java
new file mode 100644
index 0000000..1a1f4bf
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_hlen.java
@@ -0,0 +1,20 @@
+package com.github.fppt.jedismock.operations;
+
+import com.github.fppt.jedismock.server.Response;
+import com.github.fppt.jedismock.server.Slice;
+import com.github.fppt.jedismock.storage.RedisBase;
+
+import java.util.List;
+import java.util.Map;
+
+public class RO_hlen extends AbstractRedisOperation {
+    public RO_hlen(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    Slice response() {
+        Slice key = params().get(0);
+        Map<Slice, Slice> map = base().getFieldsAndValues(key);
+        return Response.integer(map.size());
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_zrangebylex.java b/src/main/java/com/github/fppt/jedismock/operations/RO_zrangebylex.java
new file mode 100644
index 0000000..8eaabd6
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_zrangebylex.java
@@ -0,0 +1,62 @@
+package com.github.fppt.jedismock.operations;
+
+import com.github.fppt.jedismock.server.Response;
+import com.github.fppt.jedismock.server.Slice;
+import com.github.fppt.jedismock.storage.RedisBase;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+class RO_zrangebylex extends AbstractRedisOperation {
+
+    private static final String START_UNBOUNDED = "-";
+    private static final String END_UNBOUNDED = "+";
+    private static final String INCLUSIVE_PREFIX = "[";
+    private static final String EXCLUSIVE_PREFIX = "(";
+
+    RO_zrangebylex(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    @Override
+    Slice response() {
+        Slice key = params().get(0);
+        LinkedHashMap<Slice, Double> map = getDataFromBase(key, new LinkedHashMap<>());
+
+        String start = params().get(1).toString();
+        if (!START_UNBOUNDED.equals(start)
+                && !start.startsWith(INCLUSIVE_PREFIX)
+                && !start.startsWith(EXCLUSIVE_PREFIX)) {
+            throw new RuntimeException("Valid start must start with '" + INCLUSIVE_PREFIX + "' or '"
+                    + EXCLUSIVE_PREFIX + "'");
+        }
+
+        final Predicate<Slice> compareToStart = p -> START_UNBOUNDED.equals(start) ||
+                (start.startsWith(INCLUSIVE_PREFIX)
+                        ? p.toString().compareTo(start.substring(1)) >= 0
+                        : p.toString().compareTo(start.substring(1)) > 0);
+
+        String end = params().get(2).toString();
+        if (!END_UNBOUNDED.equals(end)
+                && !end.startsWith(INCLUSIVE_PREFIX)
+                && !end.startsWith(EXCLUSIVE_PREFIX)) {
+            throw new RuntimeException("Valid end must start with '" + INCLUSIVE_PREFIX + "' or '"
+                    + EXCLUSIVE_PREFIX + "'");
+        }
+
+        final Predicate<Slice> compareToEnd = p -> END_UNBOUNDED.equals(end) ||
+                (end.startsWith(INCLUSIVE_PREFIX)
+                        ? p.toString().compareTo(end.substring(1)) <= 0
+                        : p.toString().compareTo(end.substring(1)) < 0);
+
+        List<Slice> values = map.keySet().stream()
+                .filter(compareToStart.and(compareToEnd))
+                .sorted()
+                .map(Response::bulkString)
+                .collect(Collectors.toList());
+
+        return Response.array(values);
+    }
+}


Test patch:
diff --git a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
index c8112e0..3e4e299 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
@@ -361,6 +361,18 @@ public class SimpleOperationsTest extends ComparisonBase {
         assertEquals(result, toCompare);
     }
 
+    @Theory
+    public void whenHLen_EnsureCorrectLengthReturned(Jedis jedis) {
+        jedis.flushDB();
+
+        jedis.hset(HASH, FIELD_1, VALUE_1);
+        jedis.hset(HASH, FIELD_2, VALUE_2);
+
+        long result = jedis.hlen(HASH);
+
+        assertEquals(2, result);
+    }
+
     @Theory
     public void whenUsingHsinter_EnsureSetIntersectionIsReturned(Jedis jedis) {
         String key1 = "my-set-key-1";
@@ -770,4 +782,34 @@ public class SimpleOperationsTest extends ComparisonBase {
         assertEquals("myvalue4", results.get(3).getElement());
         assertEquals(20d, results.get(3).getScore(), 0);
     }
+
+    @Theory
+    public void zrangebylexKeysCorrectOrder(Jedis jedis) {
+        jedis.flushDB();
+
+        String key = "mykey";
+        Map<String, Double> members = new HashMap<>();
+        members.put("bbb", 0d);
+        members.put("ddd", 0d);
+        members.put("ccc", 0d);
+        members.put("aaa", 0d);
+
+        long result = jedis.zadd(key, members);
+
+        assertEquals(4L, result);
+
+        List<String> results = new ArrayList<>(jedis.zrangeByLex(key, "-", "+"));
+
+        assertEquals(4, results.size());
+        assertEquals("aaa", results.get(0));
+        assertEquals("bbb", results.get(1));
+        assertEquals("ccc", results.get(2));
+        assertEquals("ddd", results.get(3));
+
+        results = new ArrayList<>(jedis.zrangeByLex(key, "[bbb", "(ddd"));
+
+        assertEquals(2, results.size());
+        assertEquals("bbb", results.get(0));
+        assertEquals("ccc", results.get(1));
+    }
 }