Repository: fppt/jedis-mock
Pull request #50 (2019-06-21 20:02:13)

Adding support for `zcard` command.
Adding support for `WITHSCORES` parameter on `zrange` command.

Also, the score is actually stored as a Double, so I'm using that as the type for the LinkedHashMap.


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 f71b2f4..067721c 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/OperationFactory.java
@@ -59,6 +59,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("keys", RO_keys::new);
         TRANSACTIONAL_OPERATIONS.put("sadd", RO_sadd::new);
         TRANSACTIONAL_OPERATIONS.put("scan", RO_scan::new);
+        TRANSACTIONAL_OPERATIONS.put("sscan", RO_sscan::new);
         TRANSACTIONAL_OPERATIONS.put("spop", RO_spop::new);
         TRANSACTIONAL_OPERATIONS.put("srem", RO_srem::new);
         TRANSACTIONAL_OPERATIONS.put("scard", RO_scard::new);
@@ -75,6 +76,7 @@ public class OperationFactory {
         TRANSACTIONAL_OPERATIONS.put("blpop", RO_blpop::new);
         TRANSACTIONAL_OPERATIONS.put("brpop", RO_brpop::new);
         TRANSACTIONAL_OPERATIONS.put("zadd", RO_zadd::new);
+        TRANSACTIONAL_OPERATIONS.put("zcard", RO_zcard::new);
         TRANSACTIONAL_OPERATIONS.put("zrange", RO_zrange::new);
         TRANSACTIONAL_OPERATIONS.put("zrem", RO_zrem::new);
     }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java b/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
index 9d6a695..a8f1b66 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_scan.java
@@ -21,32 +21,34 @@ class RO_scan extends AbstractRedisOperation {
     private static final String MATCH = "match";
     private static final String COUNT = "count";
 
+    protected Slice cursorSlice;
+    protected int size;
+
     RO_scan(RedisBase base, List<Slice> params) {
         super(base, params);
     }
 
-    Slice response() {
+    @Override
+    void doOptionalWork() {
+        this.cursorSlice = params().get(0);
+        this.size = base().keys().size();
+    }
 
-        Slice cursorSlice = params().get(0);
+    Slice response() {
         long cursor = cursorSlice != null ? convertToLong(cursorSlice.toString()) : CURSOR_START;
 
         String match = extractParameter(params(), MATCH).map(Slice::toString).orElse("*");
         long count = extractParameter(params(), COUNT).map(s -> convertToLong(s.toString())).orElse(DEFAULT_COUNT);
 
         String regex = Utils.createRegexFromGlob(match);
-        List<Slice> matchingKeys = base().keys().stream()
-                .skip(cursor)
-                .limit(count)
-                .filter(x -> x.toString().matches(regex))
-                .map(Response::bulkString)
-                .collect(Collectors.toList());
+        List<Slice> matchingValues = getMatchingValues(regex, cursor, count);
 
         cursor = cursor + count;
-        if (cursor >= base().keys().size()) {
+        if (cursor >= size) {
             cursor = CURSOR_START;
         }
 
-        List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingKeys));
+        List<Slice> response = Lists.newArrayList(Response.bulkString(Slice.create(String.valueOf(cursor))), Response.array(matchingValues));
         return Response.array(response);
     }
 
@@ -59,4 +61,13 @@ class RO_scan extends AbstractRedisOperation {
         }
         return Optional.empty();
     }
+
+    protected List<Slice> getMatchingValues(String regex, long cursor, long count) {
+        return base().keys().stream()
+                .skip(cursor)
+                .limit(count)
+                .filter(x -> x.toString().matches(regex))
+                .map(Response::bulkString)
+                .collect(Collectors.toList());
+    }
 }
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java b/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java
new file mode 100644
index 0000000..d756716
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_sscan.java
@@ -0,0 +1,37 @@
+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.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+class RO_sscan extends RO_scan {
+
+    RO_sscan(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    private Slice keySlice;
+
+    @Override
+    void doOptionalWork() {
+        this.keySlice = params().get(0);
+        this.cursorSlice = params().get(1);
+        this.size = base().keys().size();
+    }
+
+    @Override
+    protected List<Slice> getMatchingValues(String regex, long cursor, long count) {
+        Set<Slice> set = getDataFromBase(keySlice, new HashSet<>());
+        this.size = set.size();
+        return set.stream().skip(cursor)
+                .limit(count)
+                .filter(x -> x.toString().matches(regex))
+                .map(Response::bulkString)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_zadd.java b/src/main/java/com/github/fppt/jedismock/operations/RO_zadd.java
index 916df9a..2092b55 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/RO_zadd.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_zadd.java
@@ -22,7 +22,7 @@ class RO_zadd extends AbstractRedisOperation {
     @Override
     Slice response() {
         Slice key = params().get(0);
-        LinkedHashMap<Slice, Slice> map = getDataFromBase(key, new LinkedHashMap<>());
+        LinkedHashMap<Slice, Double> map = getDataFromBase(key, new LinkedHashMap<>());
 
         int count = 0;
         for (int i = 1; i < params().size(); i += 2) {
@@ -30,9 +30,9 @@ class RO_zadd extends AbstractRedisOperation {
             Slice value = params().get(i + 1);
 
             // Score must be a double. Will throw an exception if it's not.
-            convertToDouble(score.toString());
+            double s = convertToDouble(score.toString());
 
-            Slice prevScore = map.put(value, score);
+            Double prevScore = map.put(value, s);
             if (prevScore == null) count++;
         }
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_zcard.java b/src/main/java/com/github/fppt/jedismock/operations/RO_zcard.java
new file mode 100644
index 0000000..dcf5923
--- /dev/null
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_zcard.java
@@ -0,0 +1,24 @@
+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.Map;
+
+class RO_zcard extends AbstractRedisOperation {
+
+    RO_zcard(RedisBase base, List<Slice> params) {
+        super(base, params);
+    }
+
+    @Override
+    Slice response() {
+        Slice key = params().get(0);
+        Map<Slice, Double> map = getDataFromBase(key, new LinkedHashMap<>());
+        if (map == null || map.isEmpty()) return Response.integer(0);
+        return Response.integer(map.size());
+    }
+}
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_zrange.java b/src/main/java/com/github/fppt/jedismock/operations/RO_zrange.java
index 8f7b0bb..27fa974 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/RO_zrange.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_zrange.java
@@ -3,6 +3,7 @@ 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 com.google.common.collect.Lists;
 
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -13,6 +14,8 @@ import static com.github.fppt.jedismock.Utils.convertToInteger;
 
 class RO_zrange extends AbstractRedisOperation {
 
+    private static final String WITH_SCORES = "WITHSCORES";
+
     RO_zrange(RedisBase base, List<Slice> params) {
         super(base, params);
     }
@@ -20,7 +23,7 @@ class RO_zrange extends AbstractRedisOperation {
     @Override
     Slice response() {
         Slice key = params().get(0);
-        Map<Slice, Slice> map = getDataFromBase(key, new LinkedHashMap<>());
+        Map<Slice, Double> map = getDataFromBase(key, new LinkedHashMap<>());
 
         int start = convertToInteger(params().get(1).toString());
         int end = convertToInteger(params().get(2).toString());
@@ -43,9 +46,14 @@ class RO_zrange extends AbstractRedisOperation {
             end = map.size() - 1;
         }
 
-        List<Slice> values = map.keySet().stream()
+        final boolean withScores = params().size() == 4 && WITH_SCORES.equalsIgnoreCase(params().get(3).toString());
+
+        List<Slice> values = map.entrySet().stream()
             .skip(start)
             .limit(end - start + 1)
+            .flatMap(e -> withScores
+                    ? Lists.newArrayList(e.getKey(), Slice.create(e.getValue().toString())).stream()
+                    : Lists.newArrayList(e.getKey()).stream())
             .map(Response::bulkString)
             .collect(Collectors.toList());
 
diff --git a/src/main/java/com/github/fppt/jedismock/operations/RO_zrem.java b/src/main/java/com/github/fppt/jedismock/operations/RO_zrem.java
index d995168..4802ece 100644
--- a/src/main/java/com/github/fppt/jedismock/operations/RO_zrem.java
+++ b/src/main/java/com/github/fppt/jedismock/operations/RO_zrem.java
@@ -17,7 +17,7 @@ class RO_zrem extends AbstractRedisOperation {
 
     Slice response() {
         Slice key = params().get(0);
-        Map<Slice, Slice> map = getDataFromBase(key, null);
+        Map<Slice, Double> map = getDataFromBase(key, null);
         if(map == null || map.isEmpty()) return Response.integer(0);
         int count = 0;
         for (int i = 1; i < params().size(); i++) {


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 f3babc4..bbad2bb 100644
--- a/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
+++ b/src/test/java/com/github/fppt/jedismock/comparisontests/SimpleOperationsTest.java
@@ -4,10 +4,7 @@ package com.github.fppt.jedismock.comparisontests;
 import org.junit.experimental.theories.Theories;
 import org.junit.experimental.theories.Theory;
 import org.junit.runner.RunWith;
-import redis.clients.jedis.Client;
-import redis.clients.jedis.Jedis;
-import redis.clients.jedis.ScanParams;
-import redis.clients.jedis.ScanResult;
+import redis.clients.jedis.*;
 import redis.clients.jedis.exceptions.JedisConnectionException;
 
 import java.util.*;
@@ -47,14 +44,14 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingRpop_EnsureTheLastElementPushedIsReturned(Jedis jedis){
+    public void whenUsingRpop_EnsureTheLastElementPushedIsReturned(Jedis jedis) {
         String key = "Another key";
         jedis.rpush(key, "1", "2", "3");
         assertEquals(jedis.rpop(key), "3");
     }
 
     @Theory
-    public void whenUsingRpoplpush_CorrectResultsAreReturned(Jedis jedis){
+    public void whenUsingRpoplpush_CorrectResultsAreReturned(Jedis jedis) {
         String list1key = "list 1";
         String list2key = "list 2";
 
@@ -91,7 +88,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis){
+    public void whenUsingFlushall_EnsureEverythingIsDeleted(Jedis jedis) {
         String key = "my-super-special-key";
         String value = "my-not-so-special-value";
 
@@ -103,7 +100,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis){
+    public void whenUsingFlushdb_EnsureEverythingIsDeleted(Jedis jedis) {
         String key = "my-super-special-key";
         String value = "my-not-so-special-value";
 
@@ -115,7 +112,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis){
+    public void whenUsingLrem_EnsureDeletionsWorkAsExpected(Jedis jedis) {
         String key = "my-super-special-sexy-key";
         String hello = "hello";
         String foo = "foo";
@@ -142,7 +139,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingQuit_EnsureTheConnectionIsClosed(Jedis jedis){
+    public void whenUsingQuit_EnsureTheConnectionIsClosed(Jedis jedis) {
         //Create a new connection
         Client client = jedis.getClient();
         Jedis newJedis = new Jedis(client.getHost(), client.getPort());
@@ -157,7 +154,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     @Theory
     public void whenConcurrentlyIncrementingAndDecrementingCount_EnsureFinalCountIsCorrect(Jedis jedis) throws ExecutionException, InterruptedException {
         String key = "my-count-tracker";
-        int [] count = new int[]{1, 5, 6, 2, -9, -2, 10, 11, 5, -2, -2};
+        int[] count = new int[]{1, 5, 6, 2, -9, -2, 10, 11, 5, -2, -2};
 
         jedis.set(key, "0");
         assertEquals(0, Integer.parseInt(jedis.get(key)));
@@ -183,12 +180,12 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenPinging_Pong(Jedis jedis){
+    public void whenPinging_Pong(Jedis jedis) {
         assertEquals("PONG", jedis.ping());
     }
 
     @Theory
-    public void whenGettingKeys_EnsureCorrectKeysAreReturned(Jedis jedis){
+    public void whenGettingKeys_EnsureCorrectKeysAreReturned(Jedis jedis) {
         jedis.flushAll();
         jedis.mset("one", "1", "two", "2", "three", "3", "four", "4");
 
@@ -209,7 +206,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenAddingToASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenAddingToASet_EnsureTheSetIsUpdated(Jedis jedis) {
         String key = "my-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
 
@@ -228,7 +225,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenAddingToASet_ensureCountIsUpdated(Jedis jedis){
+    public void whenAddingToASet_ensureCountIsUpdated(Jedis jedis) {
         String key = "my-counted-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("d", "e", "f"));
 
@@ -240,13 +237,13 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenCalledForNonExistentSet_ensureScardReturnsZero(Jedis jedis){
+    public void whenCalledForNonExistentSet_ensureScardReturnsZero(Jedis jedis) {
         String key = "non-existent";
         assertEquals(0, jedis.scard(key).intValue());
     }
 
     @Theory
-    public void whenRemovingFromASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenRemovingFromASet_EnsureTheSetIsUpdated(Jedis jedis) {
         String key = "my-set-key";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
 
@@ -265,7 +262,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenPoppingFromASet_EnsureTheSetIsUpdated(Jedis jedis){
+    public void whenPoppingFromASet_EnsureTheSetIsUpdated(Jedis jedis) {
 
         String key = "my-set-key-spop";
         Set<String> mySet = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
@@ -276,18 +273,18 @@ public class SimpleOperationsTest extends ComparisonBase {
         String poppedValue;
         do {
             poppedValue = jedis.spop(key);
-            if(poppedValue != null) assertTrue("Popped value not in set", mySet.contains(poppedValue));
+            if (poppedValue != null) assertTrue("Popped value not in set", mySet.contains(poppedValue));
         } while (poppedValue != null);
     }
 
     @Theory
-    public void whenHSettingOnTheSameKeys_EnsureReturnTypeIs1WhenKeysAreNew(Jedis jedis){
+    public void whenHSettingOnTheSameKeys_EnsureReturnTypeIs1WhenKeysAreNew(Jedis jedis) {
         assertEquals(new Long(1L), jedis.hset(HASH, FIELD_1, VALUE_1));
         assertEquals(new Long(0L), jedis.hset(HASH, FIELD_1, VALUE_1));
     }
 
     @Theory
-    public void whenHSettingAndHGetting_EnsureValuesAreSetAndRetreived(Jedis jedis){
+    public void whenHSettingAndHGetting_EnsureValuesAreSetAndRetreived(Jedis jedis) {
         String field = "my-field";
         String hash = "my-hash";
         String value = "my-value";
@@ -298,7 +295,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenHDeleting_EnsureValuesAreRemoved(Jedis jedis){
+    public void whenHDeleting_EnsureValuesAreRemoved(Jedis jedis) {
         String field = "my-field-2";
         String hash = "my-hash-2";
         String value = "my-value-2";
@@ -311,7 +308,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenHGetAll_EnsureAllKeysAndValuesReturned(Jedis jedis){
+    public void whenHGetAll_EnsureAllKeysAndValuesReturned(Jedis jedis) {
         jedis.hset(HASH, FIELD_1, VALUE_1);
         jedis.hset(HASH, FIELD_2, VALUE_2);
 
@@ -336,7 +333,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHsinter_EnsureSetIntersectionIsReturned(Jedis jedis){
+    public void whenUsingHsinter_EnsureSetIntersectionIsReturned(Jedis jedis) {
         String key1 = "my-set-key-1";
         Set<String> mySet1 = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
         String key2 = "my-set-key-2";
@@ -360,7 +357,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHMget_EnsureAllValuesReturnedForEachField(Jedis jedis){
+    public void whenUsingHMget_EnsureAllValuesReturnedForEachField(Jedis jedis) {
         jedis.hset(HASH, FIELD_1, VALUE_1);
         jedis.hset(HASH, FIELD_2, VALUE_2);
         jedis.hset(HASH, FIELD_3, VALUE_3);
@@ -376,7 +373,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHMset_EnsureAllValuesAreSetForEachField(Jedis jedis){
+    public void whenUsingHMset_EnsureAllValuesAreSetForEachField(Jedis jedis) {
         Map<String, String> map = new HashMap<>();
         map.put(FIELD_1, VALUE_1);
         map.put(FIELD_2, VALUE_2);
@@ -392,7 +389,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenUsingHsetnx_EnsureValueIsOnlyPutIfOtherValueDoesNotExist(Jedis jedis){
+    public void whenUsingHsetnx_EnsureValueIsOnlyPutIfOtherValueDoesNotExist(Jedis jedis) {
         assertNull(jedis.hget(HASH, FIELD_3));
         jedis.hsetnx(HASH, FIELD_3, VALUE_1);
         assertEquals(VALUE_1, jedis.hget(HASH, FIELD_3));
@@ -401,12 +398,12 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void whenGettingInfo_EnsureSomeDateIsReturned(Jedis jedis){
+    public void whenGettingInfo_EnsureSomeDateIsReturned(Jedis jedis) {
         assertNotNull(jedis.info());
     }
 
     @Theory
-    public void whenCreatingKeys_existsValuesUpdated(Jedis jedis){
+    public void whenCreatingKeys_existsValuesUpdated(Jedis jedis) {
         jedis.set("foo", "bar");
         assertTrue(jedis.exists("foo"));
 
@@ -417,7 +414,7 @@ public class SimpleOperationsTest extends ComparisonBase {
     }
 
     @Theory
-    public void deletionRemovesKeys(Jedis jedis){
+    public void deletionRemovesKeys(Jedis jedis) {
         String key1 = "hey_toremove";
         String key2 = "hmap_toremove";
         jedis.set(key1, "value");
@@ -492,6 +489,88 @@ public class SimpleOperationsTest extends ComparisonBase {
         assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
     }
 
+    @Theory
+    public void sscanReturnsAllValues(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[20];
+        for (int i = 0; i < 20; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().count(30));
+
+        assertEquals(20, result.getResult().size());
+        assertTrue(result.getResult().contains(values[1]));
+    }
+
+    @Theory
+    public void sscanReturnsPartialSet(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[20];
+        for (int i = 0; i < 20; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().count(13));
+        assertNotEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
+    }
+
+    @Theory
+    public void sscanReturnsMatchingSet(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[9];
+        for (int i = 0; i < 9; i++) {
+            values[i] = (21 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+
+        ScanResult<String> result = jedis.sscan(key, ScanParams.SCAN_POINTER_START, new ScanParams().match("21_value_0"));
+
+        assertEquals(ScanParams.SCAN_POINTER_START, result.getStringCursor());
+        assertEquals(1, result.getResult().size());
+        assertTrue(result.getResult().contains(values[0]));
+    }
+
+    @Theory
+    public void sscanIterates(Jedis jedis) {
+
+        jedis.flushDB();
+
+        String key = "sscankey";
+        jedis.del(key);
+        String[] values = new String[45];
+        for (int i = 0; i < 45; i++) {
+            values[i] = (45 - i) + "_value_" + i;
+        }
+        jedis.sadd(key, values);
+        String cursor = null;
+
+        int iterations = 0;
+        while (cursor == null || !cursor.equals(ScanParams.SCAN_POINTER_START)) {
+            if (cursor == null) {
+                cursor = ScanParams.SCAN_POINTER_START;
+            }
+            ScanResult<String> result = jedis.sscan(key, cursor);
+            cursor = result.getStringCursor();
+            iterations++;
+        }
+        assertEquals(5, iterations);
+    }
+
     @Theory
     public void zaddAddsKey(Jedis jedis) {
 
@@ -532,6 +611,33 @@ public class SimpleOperationsTest extends ComparisonBase {
         assertEquals("myvalue2", results.get(1));
     }
 
+    @Theory
+    public void zcardEmptyKey(Jedis jedis) {
+        jedis.flushDB();
+
+        String key = "mykey";
+
+        long result = jedis.zcard(key);
+
+        assertEquals(0L, result);
+    }
+
+    @Theory
+    public void zcardReturnsCount(Jedis jedis) {
+        jedis.flushDB();
+
+        String key = "mykey";
+        Map<String, Double> members = new HashMap<>();
+        members.put("myvalue1", 10d);
+        members.put("myvalue2", 20d);
+
+        jedis.zadd(key, members);
+
+        long result = jedis.zcard(key);
+
+        assertEquals(2L, result);
+    }
+
     @Theory
     public void zremRemovesKey(Jedis jedis) {
 
@@ -607,4 +713,32 @@ public class SimpleOperationsTest extends ComparisonBase {
 
         assertEquals(0, results.size());
     }
+
+    @Theory
+    public void zrangeWithScores(Jedis jedis) {
+        jedis.flushDB();
+
+        String key = "mykey";
+        Map<String, Double> members = new HashMap<>();
+        members.put("myvalue2", 10d);
+        members.put("myvalue4", 20d);
+        members.put("myvalue3", 15d);
+        members.put("myvalue1", 9d);
+
+        long result = jedis.zadd(key, members);
+
+        assertEquals(4L, result);
+
+        List<Tuple> results = new LinkedList<>(jedis.zrangeWithScores(key, 0, -1));
+
+        assertEquals(4, results.size());
+        assertEquals("myvalue1", results.get(0).getElement());
+        assertEquals(9d, results.get(0).getScore(), 0);
+        assertEquals("myvalue2", results.get(1).getElement());
+        assertEquals(10d, results.get(1).getScore(), 0);
+        assertEquals("myvalue3", results.get(2).getElement());
+        assertEquals(15d, results.get(2).getScore(), 0);
+        assertEquals("myvalue4", results.get(3).getElement());
+        assertEquals(20d, results.get(3).getScore(), 0);
+    }
 }