From 0b34b1909ae443b9447451e263b39b7e428651be Mon Sep 17 00:00:00 2001
From: Nedyalko Dyakov <nedyalko.dyakov@gmail.com>
Date: Wed, 5 Feb 2025 15:44:09 +0200
Subject: [PATCH] feat(command): add ACL commands, validate module categories
 exist (#3262)

* add ACL{SetUser,DelUser,List} commands

* test presence of categories in acl cat

* code cleanup

* add basic acl tests

* add acl modules tests

* reset acl log before test

* refactor acl tests

* fix clientkillbyage test
---
 acl_commands.go      |  54 ++++++
 acl_commands_test.go | 449 +++++++++++++++++++++++++++++++++++++++++++
 commands_test.go     |  55 +-----
 3 files changed, 505 insertions(+), 53 deletions(-)
 create mode 100644 acl_commands_test.go

diff --git a/acl_commands.go b/acl_commands.go
index 06847be2..9cb800bb 100644
--- a/acl_commands.go
+++ b/acl_commands.go
@@ -4,8 +4,20 @@ import "context"
 
 type ACLCmdable interface {
 	ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
+
 	ACLLog(ctx context.Context, count int64) *ACLLogCmd
 	ACLLogReset(ctx context.Context) *StatusCmd
+
+	ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd
+	ACLDelUser(ctx context.Context, username string) *IntCmd
+	ACLList(ctx context.Context) *StringSliceCmd
+
+	ACLCat(ctx context.Context) *StringSliceCmd
+	ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd
+}
+
+type ACLCatArgs struct {
+	Category string
 }
 
 func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd {
@@ -33,3 +45,45 @@ func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd {
 	_ = c(ctx, cmd)
 	return cmd
 }
+
+func (c cmdable) ACLDelUser(ctx context.Context, username string) *IntCmd {
+	cmd := NewIntCmd(ctx, "acl", "deluser", username)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd {
+	args := make([]interface{}, 3+len(rules))
+	args[0] = "acl"
+	args[1] = "setuser"
+	args[2] = username
+	for i, rule := range rules {
+		args[i+3] = rule
+	}
+	cmd := NewStatusCmd(ctx, args...)
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) ACLList(ctx context.Context) *StringSliceCmd {
+	cmd := NewStringSliceCmd(ctx, "acl", "list")
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) ACLCat(ctx context.Context) *StringSliceCmd {
+	cmd := NewStringSliceCmd(ctx, "acl", "cat")
+	_ = c(ctx, cmd)
+	return cmd
+}
+
+func (c cmdable) ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd {
+	// if there is a category passed, build new cmd, if there isn't - use the ACLCat method
+	if options != nil && options.Category != "" {
+		cmd := NewStringSliceCmd(ctx, "acl", "cat", options.Category)
+		_ = c(ctx, cmd)
+		return cmd
+	}
+
+	return c.ACLCat(ctx)
+}
diff --git a/acl_commands_test.go b/acl_commands_test.go
new file mode 100644
index 00000000..84645583
--- /dev/null
+++ b/acl_commands_test.go
@@ -0,0 +1,449 @@
+package redis_test
+
+import (
+	"context"
+
+	"github.com/redis/go-redis/v9"
+
+	. "github.com/bsm/ginkgo/v2"
+	. "github.com/bsm/gomega"
+)
+
+var TestUserName string = "goredis"
+var _ = Describe("ACL", func() {
+	var client *redis.Client
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.Background()
+		opt := redisOptions()
+		client = redis.NewClient(opt)
+	})
+
+	It("should ACL LOG", Label("NonRedisEnterprise"), func() {
+		Expect(client.ACLLogReset(ctx).Err()).NotTo(HaveOccurred())
+		err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
+		Expect(err).NotTo(HaveOccurred())
+
+		clientAcl := redis.NewClient(redisOptions())
+		clientAcl.Options().Username = "test"
+		clientAcl.Options().Password = "test"
+		clientAcl.Options().DB = 0
+		_ = clientAcl.Set(ctx, "mystring", "foo", 0).Err()
+		_ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err()
+		_ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err()
+
+		logEntries, err := client.ACLLog(ctx, 10).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(len(logEntries)).To(Equal(4))
+
+		for _, entry := range logEntries {
+			Expect(entry.Reason).To(Equal("command"))
+			Expect(entry.Context).To(Equal("toplevel"))
+			Expect(entry.Object).NotTo(BeEmpty())
+			Expect(entry.Username).To(Equal("test"))
+			Expect(entry.AgeSeconds).To(BeNumerically(">=", 0))
+			Expect(entry.ClientInfo).NotTo(BeNil())
+			Expect(entry.EntryID).To(BeNumerically(">=", 0))
+			Expect(entry.TimestampCreated).To(BeNumerically(">=", 0))
+			Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0))
+		}
+
+		limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(len(limitedLogEntries)).To(Equal(2))
+
+		// cleanup after creating the user
+		err = client.Do(ctx, "acl", "deluser", "test").Err()
+		Expect(err).NotTo(HaveOccurred())
+	})
+
+	It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() {
+		// Call ACL LOG RESET
+		resetCmd := client.ACLLogReset(ctx)
+		Expect(resetCmd.Err()).NotTo(HaveOccurred())
+		Expect(resetCmd.Val()).To(Equal("OK"))
+
+		// Verify that the log is empty after the reset
+		logEntries, err := client.ACLLog(ctx, 10).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(len(logEntries)).To(Equal(0))
+	})
+
+})
+var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() {
+	var client *redis.Client
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.Background()
+		opt := redisOptions()
+		client = redis.NewClient(opt)
+	})
+
+	AfterEach(func() {
+		_, err := client.ACLDelUser(context.Background(), TestUserName).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(client.Close()).NotTo(HaveOccurred())
+	})
+
+	It("list only default user", func() {
+		res, err := client.ACLList(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(res).To(HaveLen(1))
+		Expect(res[0]).To(ContainSubstring("default"))
+	})
+
+	It("setuser and deluser", func() {
+		res, err := client.ACLList(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(res).To(HaveLen(1))
+		Expect(res[0]).To(ContainSubstring("default"))
+
+		add, err := client.ACLSetUser(ctx, TestUserName, "nopass", "on", "allkeys", "+set", "+get").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		resAfter, err := client.ACLList(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(resAfter).To(HaveLen(2))
+		Expect(resAfter[1]).To(ContainSubstring(TestUserName))
+
+		deletedN, err := client.ACLDelUser(ctx, TestUserName).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(deletedN).To(BeNumerically("==", 1))
+
+		resAfterDeletion, err := client.ACLList(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(resAfterDeletion).To(HaveLen(1))
+		Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0]))
+	})
+
+	It("should acl dryrun", func() {
+		dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey")
+		Expect(dryRun.Err()).NotTo(HaveOccurred())
+		Expect(dryRun.Val()).To(Equal("OK"))
+	})
+})
+
+var _ = Describe("ACL permissions", Label("NonRedisEnterprise"), func() {
+	var client *redis.Client
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.Background()
+		opt := redisOptions()
+		opt.UnstableResp3 = true
+		client = redis.NewClient(opt)
+	})
+
+	AfterEach(func() {
+		_, err := client.ACLDelUser(context.Background(), TestUserName).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(client.Close()).NotTo(HaveOccurred())
+	})
+
+	It("reset permissions", func() {
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		connection := client.Conn()
+		authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		_, err = connection.Get(ctx, "anykey").Result()
+		Expect(err).To(HaveOccurred())
+	})
+
+	It("add write permissions", func() {
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+			"~*",
+			"+SET",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		connection := client.Conn()
+		authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		// can write
+		v, err := connection.Set(ctx, "anykey", "anyvalue", 0).Result()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(v).To(Equal("OK"))
+
+		// but can't read
+		value, err := connection.Get(ctx, "anykey").Result()
+		Expect(err).To(HaveOccurred())
+		Expect(value).To(BeEmpty())
+	})
+
+	It("add read permissions", func() {
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+			"~*",
+			"+GET",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		connection := client.Conn()
+		authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		// can read
+		value, err := connection.Get(ctx, "anykey").Result()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(value).To(Equal("anyvalue"))
+
+		// but can't delete
+		del, err := connection.Del(ctx, "anykey").Result()
+		Expect(err).To(HaveOccurred())
+		Expect(del).ToNot(Equal(1))
+	})
+
+	It("add del permissions", func() {
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+			"~*",
+			"+DEL",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		connection := client.Conn()
+		authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		// can read
+		del, err := connection.Del(ctx, "anykey").Result()
+		Expect(err).ToNot(HaveOccurred())
+		Expect(del).To(BeEquivalentTo(1))
+	})
+
+	It("set permissions for module commands", func() {
+		SkipBeforeRedisMajor(8, "permissions for modules are supported for Redis Version >=8")
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+		val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).To(BeEquivalentTo("OK"))
+		WaitForIndexing(client, "txt")
+		client.HSet(ctx, "doc1", "txt", "foo baz")
+		client.HSet(ctx, "doc2", "txt", "foo bar")
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+			"~*",
+			"+FT.SEARCH",
+			"-FT.DROPINDEX",
+			"+json.set",
+			"+json.get",
+			"-json.clear",
+			"+bf.reserve",
+			"-bf.info",
+			"+cf.reserve",
+			"+cms.initbydim",
+			"+topk.reserve",
+			"+tdigest.create",
+			"+ts.create",
+			"-ts.info",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		c := client.Conn()
+		authed, err := c.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		// has perm for search
+		Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred())
+
+		// no perm for dropindex
+		err = c.FTDropIndex(ctx, "txt").Err()
+		Expect(err).ToNot(BeEmpty())
+		Expect(err.Error()).To(ContainSubstring("NOPERM"))
+
+		// json set and get have perm
+		Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred())
+		Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]"))
+
+		// no perm for json clear
+		err = c.JSONClear(ctx, "foo", "$").Err()
+		Expect(err).ToNot(BeEmpty())
+		Expect(err.Error()).To(ContainSubstring("NOPERM"))
+
+		// perm for reserve
+		Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred())
+
+		// no perm for info
+		err = c.BFInfo(ctx, "bloom").Err()
+		Expect(err).ToNot(BeEmpty())
+		Expect(err.Error()).To(ContainSubstring("NOPERM"))
+
+		// perm for cf.reserve
+		Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred())
+		// perm for cms.initbydim
+		Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred())
+		// perm for topk.reserve
+		Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred())
+		// perm for tdigest.create
+		Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred())
+		// perm for ts.create
+		Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred())
+		// noperm for ts.info
+		err = c.TSInfo(ctx, "tsts").Err()
+		Expect(err).ToNot(BeEmpty())
+		Expect(err.Error()).To(ContainSubstring("NOPERM"))
+
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+	})
+
+	It("set permissions for module categories", func() {
+		SkipBeforeRedisMajor(8, "permissions for modules are supported for Redis Version >=8")
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+		val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(val).To(BeEquivalentTo("OK"))
+		WaitForIndexing(client, "txt")
+		client.HSet(ctx, "doc1", "txt", "foo baz")
+		client.HSet(ctx, "doc2", "txt", "foo bar")
+		add, err := client.ACLSetUser(ctx,
+			TestUserName,
+			"reset",
+			"nopass",
+			"on",
+			"~*",
+			"+@search",
+			"+@json",
+			"+@bloom",
+			"+@cuckoo",
+			"+@topk",
+			"+@cms",
+			"+@timeseries",
+			"+@tdigest",
+		).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(add).To(Equal("OK"))
+
+		c := client.Conn()
+		authed, err := c.AuthACL(ctx, TestUserName, "").Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(authed).To(Equal("OK"))
+
+		// has perm for search
+		Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred())
+		// perm for dropindex
+		Expect(c.FTDropIndex(ctx, "txt").Err()).NotTo(HaveOccurred())
+		// json set and get have perm
+		Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred())
+		Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]"))
+		// perm for json clear
+		Expect(c.JSONClear(ctx, "foo", "$").Err()).NotTo(HaveOccurred())
+		// perm for reserve
+		Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred())
+		// perm for info
+		Expect(c.BFInfo(ctx, "bloom").Err()).NotTo(HaveOccurred())
+		// perm for cf.reserve
+		Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred())
+		// perm for cms.initbydim
+		Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred())
+		// perm for topk.reserve
+		Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred())
+		// perm for tdigest.create
+		Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred())
+		// perm for ts.create
+		Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred())
+		// perm for ts.info
+		Expect(c.TSInfo(ctx, "tsts").Err()).NotTo(HaveOccurred())
+
+		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
+	})
+})
+
+var _ = Describe("ACL Categories", func() {
+	var client *redis.Client
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.Background()
+		opt := redisOptions()
+		client = redis.NewClient(opt)
+	})
+
+	AfterEach(func() {
+		Expect(client.Close()).NotTo(HaveOccurred())
+	})
+
+	It("lists acl categories and subcategories", func() {
+		res, err := client.ACLCat(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(len(res)).To(BeNumerically(">", 20))
+		Expect(res).To(ContainElements(
+			"read",
+			"write",
+			"keyspace",
+			"dangerous",
+			"slow",
+			"set",
+			"sortedset",
+			"list",
+			"hash",
+		))
+
+		res, err = client.ACLCatArgs(ctx, &redis.ACLCatArgs{Category: "read"}).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(res).To(ContainElement("get"))
+	})
+
+	It("lists acl categories and subcategories with Modules", func() {
+		SkipBeforeRedisMajor(8, "modules are included in acl for redis version >= 8")
+		aclTestCase := map[string]string{
+			"search":     "FT.CREATE",
+			"bloom":      "bf.add",
+			"json":       "json.get",
+			"cuckoo":     "cf.insert",
+			"cms":        "cms.query",
+			"topk":       "topk.list",
+			"tdigest":    "tdigest.rank",
+			"timeseries": "ts.range",
+		}
+		var cats []interface{}
+
+		for cat, subitem := range aclTestCase {
+			cats = append(cats, cat)
+
+			res, err := client.ACLCatArgs(ctx, &redis.ACLCatArgs{
+				Category: cat,
+			}).Result()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(res).To(ContainElement(subitem))
+		}
+
+		res, err := client.ACLCat(ctx).Result()
+		Expect(err).NotTo(HaveOccurred())
+		Expect(res).To(ContainElements(cats...))
+	})
+})
diff --git a/commands_test.go b/commands_test.go
index dacc7f3d..404ffd02 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -211,13 +211,13 @@ var _ = Describe("Commands", func() {
 			select {
 			case <-done:
 				Fail("BLPOP is not blocked.")
-			case <-time.After(2 * time.Second):
+			case <-time.After(1 * time.Second):
 				// ok
 			}
 
 			killed := client.ClientKillByFilter(ctx, "MAXAGE", "1")
 			Expect(killed.Err()).NotTo(HaveOccurred())
-			Expect(killed.Val()).To(SatisfyAny(Equal(int64(2)), Equal(int64(3))))
+			Expect(killed.Val()).To(SatisfyAny(Equal(int64(2)), Equal(int64(3)), Equal(int64(4))))
 
 			select {
 			case <-done:
@@ -2228,12 +2228,6 @@ var _ = Describe("Commands", func() {
 			Expect(replace.Val()).To(Equal(int64(1)))
 		})
 
-		It("should acl dryrun", func() {
-			dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey")
-			Expect(dryRun.Err()).NotTo(HaveOccurred())
-			Expect(dryRun.Val()).To(Equal("OK"))
-		})
-
 		It("should fail module loadex", Label("NonRedisEnterprise"), func() {
 			dryRun := client.ModuleLoadex(ctx, &redis.ModuleLoadexConfig{
 				Path: "/path/to/non-existent-library.so",
@@ -2281,51 +2275,6 @@ var _ = Describe("Commands", func() {
 
 			Expect(args).To(Equal(expectedArgs))
 		})
-
-		It("should ACL LOG", Label("NonRedisEnterprise"), func() {
-			err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
-			Expect(err).NotTo(HaveOccurred())
-
-			clientAcl := redis.NewClient(redisOptions())
-			clientAcl.Options().Username = "test"
-			clientAcl.Options().Password = "test"
-			clientAcl.Options().DB = 0
-			_ = clientAcl.Set(ctx, "mystring", "foo", 0).Err()
-			_ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err()
-			_ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err()
-
-			logEntries, err := client.ACLLog(ctx, 10).Result()
-			Expect(err).NotTo(HaveOccurred())
-			Expect(len(logEntries)).To(Equal(4))
-
-			for _, entry := range logEntries {
-				Expect(entry.Reason).To(Equal("command"))
-				Expect(entry.Context).To(Equal("toplevel"))
-				Expect(entry.Object).NotTo(BeEmpty())
-				Expect(entry.Username).To(Equal("test"))
-				Expect(entry.AgeSeconds).To(BeNumerically(">=", 0))
-				Expect(entry.ClientInfo).NotTo(BeNil())
-				Expect(entry.EntryID).To(BeNumerically(">=", 0))
-				Expect(entry.TimestampCreated).To(BeNumerically(">=", 0))
-				Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0))
-			}
-
-			limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
-			Expect(err).NotTo(HaveOccurred())
-			Expect(len(limitedLogEntries)).To(Equal(2))
-		})
-
-		It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() {
-			// Call ACL LOG RESET
-			resetCmd := client.ACLLogReset(ctx)
-			Expect(resetCmd.Err()).NotTo(HaveOccurred())
-			Expect(resetCmd.Val()).To(Equal("OK"))
-
-			// Verify that the log is empty after the reset
-			logEntries, err := client.ACLLog(ctx, 10).Result()
-			Expect(err).NotTo(HaveOccurred())
-			Expect(len(logEntries)).To(Equal(0))
-		})
 	})
 
 	Describe("hashes", func() {