kirito632 commented on code in PR #3475:
URL: https://github.com/apache/kvrocks/pull/3475#discussion_r3185597843


##########
tests/gocase/unit/type/strings/strings_test.go:
##########
@@ -1252,3 +1252,397 @@ func testString(t *testing.T, configs 
util.KvrocksServerConfigs) {
                require.Equal(t, value, rdb.Get(ctx, key).Val())
        })
 }
+
+func TestSetConditional(t *testing.T) {
+       srv := util.StartServer(t, map[string]string{})
+       defer srv.Close()
+       ctx := context.Background()
+       rdb := srv.NewClient()
+       defer func() { require.NoError(t, rdb.Close()) }()
+
+       // ── 6.1 Syntax / parse error cases 
──────────────────────────────────────
+
+       t.Run("IFEQ missing cmp_value returns error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "IFEQ").Err()
+               require.Error(t, err)
+       })
+
+       t.Run("IFNE missing cmp_value returns error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "IFNE").Err()
+               require.Error(t, err)
+       })
+
+       t.Run("IFDEQ missing cmp_value returns error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "IFDEQ").Err()
+               require.Error(t, err)
+       })
+
+       t.Run("IFDNE missing cmp_value returns error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "IFDNE").Err()
+               require.Error(t, err)
+       })
+
+       t.Run("NX and IFEQ together returns syntax error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "NX", "IFEQ", "x").Err()
+               require.Error(t, err)
+               require.Contains(t, err.Error(), "syntax")
+       })
+
+       t.Run("XX and IFNE together returns syntax error", func(t *testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "XX", "IFNE", "x").Err()
+               require.Error(t, err)
+               require.Contains(t, err.Error(), "syntax")
+       })
+
+       t.Run("IFEQ and IFDEQ together returns syntax error", func(t 
*testing.T) {
+               err := rdb.Do(ctx, "SET", "k", "v", "IFEQ", "x", "IFDEQ", 
"y").Err()
+               require.Error(t, err)
+               require.Contains(t, err.Error(), "syntax")
+       })
+
+       t.Run("WRONGTYPE error when key is not a string", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "listkey").Err())
+               require.NoError(t, rdb.RPush(ctx, "listkey", "a").Err())
+               err := rdb.Do(ctx, "SET", "listkey", "v", "IFEQ", "a").Err()
+               require.Error(t, err)
+               require.Contains(t, err.Error(), "WRONGTYPE")
+               require.NoError(t, rdb.Del(ctx, "listkey").Err())
+       })
+
+       // ── 6.2 Basic conditional behaviour 
─────────────────────────────────────
+
+       t.Run("IFEQ: key not found returns nil", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "ifeq1").Err())
+               res := rdb.Do(ctx, "SET", "ifeq1", "new", "IFEQ", 
"anything").Val()
+               require.Nil(t, res)
+       })
+
+       t.Run("IFEQ: value matches writes and returns OK", func(t *testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq2", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifeq2", "world", "IFEQ", 
"hello").Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "world", rdb.Get(ctx, "ifeq2").Val())
+       })
+
+       t.Run("IFEQ: value mismatches returns nil and no write", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq3", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifeq3", "world", "IFEQ", 
"wrong").Val()
+               require.Nil(t, res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifeq3").Val())
+       })
+
+       t.Run("IFNE: key not found writes and returns OK", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "ifne1").Err())
+               res := rdb.Do(ctx, "SET", "ifne1", "created", "IFNE", 
"anything").Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "created", rdb.Get(ctx, "ifne1").Val())
+       })
+
+       t.Run("IFNE: value matches returns nil and no write", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifne2", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifne2", "world", "IFNE", 
"hello").Val()
+               require.Nil(t, res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifne2").Val())
+       })
+
+       t.Run("IFNE: value mismatches writes and returns OK", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifne3", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifne3", "world", "IFNE", 
"wrong").Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "world", rdb.Get(ctx, "ifne3").Val())
+       })
+
+       t.Run("IFDEQ: key not found returns nil", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "ifdeq1").Err())
+               res := rdb.Do(ctx, "SET", "ifdeq1", "new", "IFDEQ", 
"xxxxxxxxxxxxxxxx").Val()
+               require.Nil(t, res)
+       })
+
+       t.Run("IFDEQ: digest matches writes and returns OK", func(t *testing.T) 
{
+               require.NoError(t, rdb.Set(ctx, "ifdeq2", "hello", 0).Err())
+               digest, err := rdb.Do(ctx, "DIGEST", "ifdeq2").Result()
+               require.NoError(t, err)
+               res := rdb.Do(ctx, "SET", "ifdeq2", "world", "IFDEQ", 
digest).Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "world", rdb.Get(ctx, "ifdeq2").Val())
+       })
+
+       t.Run("IFDEQ: digest mismatches returns nil and no write", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifdeq3", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifdeq3", "world", "IFDEQ", 
"xxxxxxxxxxxxxxxx").Val()
+               require.Nil(t, res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifdeq3").Val())
+       })
+
+       t.Run("IFDNE: key not found writes and returns OK", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "ifdne1").Err())
+               res := rdb.Do(ctx, "SET", "ifdne1", "created", "IFDNE", 
"xxxxxxxxxxxxxxxx").Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "created", rdb.Get(ctx, "ifdne1").Val())
+       })
+
+       t.Run("IFDNE: digest matches returns nil and no write", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifdne2", "hello", 0).Err())
+               digest, err := rdb.Do(ctx, "DIGEST", "ifdne2").Result()
+               require.NoError(t, err)
+               res := rdb.Do(ctx, "SET", "ifdne2", "world", "IFDNE", 
digest).Val()
+               require.Nil(t, res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifdne2").Val())
+       })
+
+       t.Run("IFDNE: digest mismatches writes and returns OK", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifdne3", "hello", 0).Err())
+               res := rdb.Do(ctx, "SET", "ifdne3", "world", "IFDNE", 
"xxxxxxxxxxxxxxxx").Val()
+               require.Equal(t, "OK", res)
+               require.Equal(t, "world", rdb.Get(ctx, "ifdne3").Val())
+       })
+
+       t.Run("IFEQ with GET: condition met returns old value", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq-get1", "old", 0).Err())
+               res, err := rdb.Do(ctx, "SET", "ifeq-get1", "new", "IFEQ", 
"old", "GET").Result()
+               require.NoError(t, err)
+               require.Equal(t, "old", res)
+               require.Equal(t, "new", rdb.Get(ctx, "ifeq-get1").Val())
+       })
+
+       t.Run("IFEQ with GET: condition not met returns old value", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq-get2", "hello", 0).Err())
+               res, err := rdb.Do(ctx, "SET", "ifeq-get2", "new", "IFEQ", 
"wrong", "GET").Result()
+               require.NoError(t, err)
+               require.Equal(t, "hello", res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifeq-get2").Val())
+       })
+
+       t.Run("IFEQ with EX: condition met sets TTL", func(t *testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq-ex1", "hello", 0).Err())
+               res, err := rdb.Do(ctx, "SET", "ifeq-ex1", "world", "IFEQ", 
"hello", "EX", "10").Result()
+               require.NoError(t, err)
+               require.Equal(t, "OK", res)
+               ttl := rdb.TTL(ctx, "ifeq-ex1").Val()
+               require.Greater(t, ttl, 8*time.Second)
+               require.LessOrEqual(t, ttl, 10*time.Second)
+       })
+
+       t.Run("IFEQ with EX: condition not met leaves TTL unchanged", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifeq-ex2", "hello", 
5*time.Second).Err())
+               res := rdb.Do(ctx, "SET", "ifeq-ex2", "world", "IFEQ", "wrong", 
"EX", "100").Val()
+               require.Nil(t, res)
+               ttl := rdb.TTL(ctx, "ifeq-ex2").Val()
+               require.Greater(t, ttl, time.Duration(0))
+               require.LessOrEqual(t, ttl, 5*time.Second)
+       })
+
+       t.Run("IFDEQ consistent with DIGEST command output", func(t *testing.T) 
{
+               require.NoError(t, rdb.Set(ctx, "digest-check", "somevalue", 
0).Err())
+               digest, err := rdb.Do(ctx, "DIGEST", "digest-check").Result()
+               require.NoError(t, err)
+               res, err := rdb.Do(ctx, "SET", "digest-check", "newvalue", 
"IFDEQ", digest).Result()
+               require.NoError(t, err)
+               require.Equal(t, "OK", res)
+       })
+
+       t.Run("IFDEQ accepts uppercase digest", func(t *testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifdeq-upper", "hello", 
0).Err())
+               digest, err := rdb.Do(ctx, "DIGEST", "ifdeq-upper").Text()
+               require.NoError(t, err)
+
+               res, err := rdb.Do(ctx, "SET", "ifdeq-upper", "world", "IFDEQ", 
strings.ToUpper(digest)).Result()
+               require.NoError(t, err)
+               require.Equal(t, "OK", res)
+               require.Equal(t, "world", rdb.Get(ctx, "ifdeq-upper").Val())
+       })
+
+       t.Run("IFDNE treats uppercase digest as a match", func(t *testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifdne-upper", "hello", 
0).Err())
+               digest, err := rdb.Do(ctx, "DIGEST", "ifdne-upper").Text()
+               require.NoError(t, err)
+
+               res := rdb.Do(ctx, "SET", "ifdne-upper", "world", "IFDNE", 
strings.ToUpper(digest)).Val()
+               require.Nil(t, res)
+               require.Equal(t, "hello", rdb.Get(ctx, "ifdne-upper").Val())
+       })
+
+       t.Run("IFDEQ and IFDNE reject malformed digest lengths", func(t 
*testing.T) {
+               require.NoError(t, rdb.Set(ctx, "ifd-bad-digest", "hello", 
0).Err())
+
+               testCases := []struct {
+                       name   string
+                       option string
+                       digest string
+               }{
+                       {name: "IFDEQ short digest", option: "IFDEQ", digest: 
"1234567890abcde"},
+                       {name: "IFDNE short digest", option: "IFDNE", digest: 
"1234567890abcde"},
+                       {name: "IFDEQ long digest", option: "IFDEQ", digest: 
"01234567890abcdef"},
+                       {name: "IFDNE long digest", option: "IFDNE", digest: 
"01234567890abcdef"},
+                       {name: "IFDEQ empty digest", option: "IFDEQ", digest: 
""},
+                       {name: "IFDNE empty digest", option: "IFDNE", digest: 
""},
+               }
+
+               for _, tc := range testCases {
+                       t.Run(tc.name, func(t *testing.T) {
+                               err := rdb.Do(ctx, "SET", "ifd-bad-digest", 
"world", tc.option, tc.digest).Err()
+                               require.ErrorContains(t, err, "must be exactly 
16 hexadecimal characters")
+                               require.Equal(t, "hello", rdb.Get(ctx, 
"ifd-bad-digest").Val())
+                       })
+               }
+       })
+
+       // ── Property tests (using testing/quick via subtests) 
───────────────────

Review Comment:
   I have updated the PR to address the two issues you pointed out (matrix 
inheritance and comments).
   I also passed the actual configs parameter into util.StartServer.
   I have pre-verified the full CI matrix on my personal fork and it is 
strictly green.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to