mirror of
https://github.com/redis/go-redis.git
synced 2025-11-14 10:22:26 +03:00
feat(cmd): Add CAS/CAD commands (#3583)
* add cas/cad commands
* feat(command): Add SetIFDEQ, SetIFDNE and *Get cmds
Decided to move the *Get argument as a separate methods, since the
response will be always the previous value, but in the case where
the previous value is `OK` there result may be ambiguous.
* fix tests
* matchValue to be interface{}
* Only Args approach for DelEx
* use uint64 for digest, add example
* test only for 8.4
This commit is contained in:
265
digest_test.go
Normal file
265
digest_test.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize RedisVersion from environment variable for regular Go tests
|
||||
// (Ginkgo tests initialize this in BeforeSuite)
|
||||
if version := os.Getenv("REDIS_VERSION"); version != "" {
|
||||
if v, err := strconv.ParseFloat(strings.Trim(version, "\""), 64); err == nil && v > 0 {
|
||||
RedisVersion = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skipIfRedisBelow84 checks if Redis version is below 8.4 and skips the test if so
|
||||
func skipIfRedisBelow84(t *testing.T) {
|
||||
if RedisVersion < 8.4 {
|
||||
t.Skipf("Skipping test: Redis version %.1f < 8.4 (DIGEST command requires Redis 8.4+)", RedisVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDigestBasic validates that the Digest command returns a uint64 value
|
||||
func TestDigestBasic(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
|
||||
client.Del(ctx, "digest-test-key")
|
||||
|
||||
// Set a value
|
||||
err := client.Set(ctx, "digest-test-key", "testvalue", 0).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set value: %v", err)
|
||||
}
|
||||
|
||||
// Get digest
|
||||
digestCmd := client.Digest(ctx, "digest-test-key")
|
||||
if err := digestCmd.Err(); err != nil {
|
||||
t.Fatalf("Failed to get digest: %v", err)
|
||||
}
|
||||
|
||||
digest := digestCmd.Val()
|
||||
if digest == 0 {
|
||||
t.Error("Digest should not be zero for non-empty value")
|
||||
}
|
||||
|
||||
t.Logf("Digest for 'testvalue': %d (0x%016x)", digest, digest)
|
||||
|
||||
// Verify same value produces same digest
|
||||
digest2 := client.Digest(ctx, "digest-test-key").Val()
|
||||
if digest != digest2 {
|
||||
t.Errorf("Same value should produce same digest: %d != %d", digest, digest2)
|
||||
}
|
||||
|
||||
client.Del(ctx, "digest-test-key")
|
||||
}
|
||||
|
||||
// TestSetIFDEQWithDigest validates the SetIFDEQ command works with digests
|
||||
func TestSetIFDEQWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
|
||||
client.Del(ctx, "cas-test-key")
|
||||
|
||||
// Set initial value
|
||||
initialValue := "initial-value"
|
||||
err := client.Set(ctx, "cas-test-key", initialValue, 0).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set initial value: %v", err)
|
||||
}
|
||||
|
||||
// Get current digest
|
||||
correctDigest := client.Digest(ctx, "cas-test-key").Val()
|
||||
wrongDigest := uint64(12345) // arbitrary wrong digest
|
||||
|
||||
// Test 1: SetIFDEQ with correct digest should succeed
|
||||
result := client.SetIFDEQ(ctx, "cas-test-key", "new-value", correctDigest, 0)
|
||||
if err := result.Err(); err != nil {
|
||||
t.Errorf("SetIFDEQ with correct digest failed: %v", err)
|
||||
} else {
|
||||
t.Logf("✓ SetIFDEQ with correct digest succeeded")
|
||||
}
|
||||
|
||||
// Verify value was updated
|
||||
val, err := client.Get(ctx, "cas-test-key").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get value: %v", err)
|
||||
}
|
||||
if val != "new-value" {
|
||||
t.Errorf("Value not updated: got %q, want %q", val, "new-value")
|
||||
}
|
||||
|
||||
// Test 2: SetIFDEQ with wrong digest should fail
|
||||
result = client.SetIFDEQ(ctx, "cas-test-key", "another-value", wrongDigest, 0)
|
||||
if result.Err() != redis.Nil {
|
||||
t.Errorf("SetIFDEQ with wrong digest should return redis.Nil, got: %v", result.Err())
|
||||
} else {
|
||||
t.Logf("✓ SetIFDEQ with wrong digest correctly failed")
|
||||
}
|
||||
|
||||
// Verify value was NOT updated
|
||||
val, err = client.Get(ctx, "cas-test-key").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get value: %v", err)
|
||||
}
|
||||
if val != "new-value" {
|
||||
t.Errorf("Value should not have changed: got %q, want %q", val, "new-value")
|
||||
}
|
||||
|
||||
client.Del(ctx, "cas-test-key")
|
||||
}
|
||||
|
||||
// TestSetIFDNEWithDigest validates the SetIFDNE command works with digests
|
||||
func TestSetIFDNEWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
|
||||
client.Del(ctx, "cad-test-key")
|
||||
|
||||
// Set initial value
|
||||
initialValue := "initial-value"
|
||||
err := client.Set(ctx, "cad-test-key", initialValue, 0).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set initial value: %v", err)
|
||||
}
|
||||
|
||||
// Use an arbitrary different digest
|
||||
wrongDigest := uint64(99999) // arbitrary different digest
|
||||
|
||||
// Test 1: SetIFDNE with different digest should succeed
|
||||
result := client.SetIFDNE(ctx, "cad-test-key", "new-value", wrongDigest, 0)
|
||||
if err := result.Err(); err != nil {
|
||||
t.Errorf("SetIFDNE with different digest failed: %v", err)
|
||||
} else {
|
||||
t.Logf("✓ SetIFDNE with different digest succeeded")
|
||||
}
|
||||
|
||||
// Verify value was updated
|
||||
val, err := client.Get(ctx, "cad-test-key").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get value: %v", err)
|
||||
}
|
||||
if val != "new-value" {
|
||||
t.Errorf("Value not updated: got %q, want %q", val, "new-value")
|
||||
}
|
||||
|
||||
// Test 2: SetIFDNE with matching digest should fail
|
||||
newDigest := client.Digest(ctx, "cad-test-key").Val()
|
||||
result = client.SetIFDNE(ctx, "cad-test-key", "another-value", newDigest, 0)
|
||||
if result.Err() != redis.Nil {
|
||||
t.Errorf("SetIFDNE with matching digest should return redis.Nil, got: %v", result.Err())
|
||||
} else {
|
||||
t.Logf("✓ SetIFDNE with matching digest correctly failed")
|
||||
}
|
||||
|
||||
// Verify value was NOT updated
|
||||
val, err = client.Get(ctx, "cad-test-key").Result()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get value: %v", err)
|
||||
}
|
||||
if val != "new-value" {
|
||||
t.Errorf("Value should not have changed: got %q, want %q", val, "new-value")
|
||||
}
|
||||
|
||||
client.Del(ctx, "cad-test-key")
|
||||
}
|
||||
|
||||
// TestDelExArgsWithDigest validates DelExArgs works with digest matching
|
||||
func TestDelExArgsWithDigest(t *testing.T) {
|
||||
skipIfRedisBelow84(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
t.Skipf("Redis not available: %v", err)
|
||||
}
|
||||
|
||||
client.Del(ctx, "del-test-key")
|
||||
|
||||
// Set a value
|
||||
value := "delete-me"
|
||||
err := client.Set(ctx, "del-test-key", value, 0).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set value: %v", err)
|
||||
}
|
||||
|
||||
// Get correct digest
|
||||
correctDigest := client.Digest(ctx, "del-test-key").Val()
|
||||
wrongDigest := uint64(54321)
|
||||
|
||||
// Test 1: Delete with wrong digest should fail
|
||||
deleted := client.DelExArgs(ctx, "del-test-key", redis.DelExArgs{
|
||||
Mode: "IFDEQ",
|
||||
MatchDigest: wrongDigest,
|
||||
}).Val()
|
||||
|
||||
if deleted != 0 {
|
||||
t.Errorf("Delete with wrong digest should not delete: got %d deletions", deleted)
|
||||
} else {
|
||||
t.Logf("✓ DelExArgs with wrong digest correctly refused to delete")
|
||||
}
|
||||
|
||||
// Verify key still exists
|
||||
exists := client.Exists(ctx, "del-test-key").Val()
|
||||
if exists != 1 {
|
||||
t.Errorf("Key should still exist after failed delete")
|
||||
}
|
||||
|
||||
// Test 2: Delete with correct digest should succeed
|
||||
deleted = client.DelExArgs(ctx, "del-test-key", redis.DelExArgs{
|
||||
Mode: "IFDEQ",
|
||||
MatchDigest: correctDigest,
|
||||
}).Val()
|
||||
|
||||
if deleted != 1 {
|
||||
t.Errorf("Delete with correct digest should delete: got %d deletions", deleted)
|
||||
} else {
|
||||
t.Logf("✓ DelExArgs with correct digest successfully deleted")
|
||||
}
|
||||
|
||||
// Verify key was deleted
|
||||
exists = client.Exists(ctx, "del-test-key").Val()
|
||||
if exists != 0 {
|
||||
t.Errorf("Key should not exist after successful delete")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user