From 061b55f97641a3cd534f5a1c92907f3af16a5ae6 Mon Sep 17 00:00:00 2001 From: Petya Slavova Date: Tue, 28 Jan 2025 17:48:29 +0200 Subject: [PATCH] Adding tests for modules ACL and modules config changes in 8.0 --- tests/test_asyncio/test_commands.py | 199 ++++++++++++++++++++++++++++ tests/test_commands.py | 197 +++++++++++++++++++++++++++ 2 files changed, 396 insertions(+) diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py index f6ed07fab5..4f43506bae 100644 --- a/tests/test_asyncio/test_commands.py +++ b/tests/test_asyncio/test_commands.py @@ -20,6 +20,9 @@ parse_info, ) from redis.client import EMPTY_RESPONSE, NEVER_DECODE +from redis.commands.json.path import Path +from redis.commands.search.field import TextField +from redis.commands.search.query import Query from tests.conftest import ( assert_resp_response, assert_resp_response_in, @@ -49,6 +52,12 @@ def factory(username): return r yield factory + try: + current_user = await r.client_info() + except exceptions.NoPermissionError: + current_user = {} + if "default" != current_user.get("user"): + await r.auth("", "default") for username in usernames: await r.acl_deluser(username) @@ -115,12 +124,65 @@ async def test_acl_cat_no_category(self, r: redis.Redis): assert isinstance(categories, list) assert "read" in categories or b"read" in categories + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_acl_cat_contain_modules_no_category(self, r: redis.Redis): + modules_list = [ + "search", + "bloom", + "json", + "cuckoo", + "timeseries", + "cms", + "topk", + "tdigest", + ] + categories = await r.acl_cat() + assert isinstance(categories, list) + for module_cat in modules_list: + assert module_cat in categories or module_cat.encode() in categories + @skip_if_server_version_lt(REDIS_6_VERSION) async def test_acl_cat_with_category(self, r: redis.Redis): commands = await r.acl_cat("read") assert isinstance(commands, list) assert "get" in commands or b"get" in commands + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_acl_modules_cat_with_category(self, r: redis.Redis): + search_commands = await r.acl_cat("search") + assert isinstance(search_commands, list) + assert "FT.SEARCH" in search_commands or b"FT.SEARCH" in search_commands + + bloom_commands = await r.acl_cat("bloom") + assert isinstance(bloom_commands, list) + assert "bf.add" in bloom_commands or b"bf.add" in bloom_commands + + json_commands = await r.acl_cat("json") + assert isinstance(json_commands, list) + assert "json.get" in json_commands or b"json.get" in json_commands + + cuckoo_commands = await r.acl_cat("cuckoo") + assert isinstance(cuckoo_commands, list) + assert "cf.insert" in cuckoo_commands or b"cf.insert" in cuckoo_commands + + cms_commands = await r.acl_cat("cms") + assert isinstance(cms_commands, list) + assert "cms.query" in cms_commands or b"cms.query" in cms_commands + + topk_commands = await r.acl_cat("topk") + assert isinstance(topk_commands, list) + assert "topk.list" in topk_commands or b"topk.list" in topk_commands + + tdigest_commands = await r.acl_cat("tdigest") + assert isinstance(tdigest_commands, list) + assert "tdigest.rank" in tdigest_commands or b"tdigest.rank" in tdigest_commands + + timeseries_commands = await r.acl_cat("timeseries") + assert isinstance(timeseries_commands, list) + assert "ts.range" in timeseries_commands or b"ts.range" in timeseries_commands + @skip_if_server_version_lt(REDIS_6_VERSION) async def test_acl_deluser(self, r_teardown): username = "redis-py-user" @@ -316,6 +378,116 @@ async def test_acl_whoami(self, r: redis.Redis): username = await r.acl_whoami() assert isinstance(username, (str, bytes)) + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_acl_modules_commands(self, r_teardown): + username = "redis-py-user" + password = "pass-for-test-user" + + r = r_teardown(username) + await r.flushdb() + + await r.ft().create_index((TextField("txt"),)) + await r.hset("doc1", mapping={"txt": "foo baz"}) + await r.hset("doc2", mapping={"txt": "foo bar"}) + + await r.acl_setuser( + username, + enabled=True, + reset=True, + passwords=[f"+{password}"], + categories=["-all"], + commands=[ + "+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", + ], + keys=["*"], + ) + + await r.auth(password, username) + + assert await r.ft().search(Query("foo ~bar")) + with pytest.raises(exceptions.NoPermissionError): + await r.ft().dropindex() + + await r.json().set("foo", Path.root_path(), "bar") + assert await r.json().get("foo") == "bar" + with pytest.raises(exceptions.NoPermissionError): + await r.json().clear("foo") + + assert await r.bf().create("bloom", 0.01, 1000) + assert await r.cf().create("cuckoo", 1000) + assert await r.cms().initbydim("cmsDim", 100, 5) + assert await r.topk().reserve("topk", 5, 100, 5, 0.9) + assert await r.tdigest().create("to-tDigest", 10) + with pytest.raises(exceptions.NoPermissionError): + await r.bf().info("bloom") + + assert await r.ts().create(1, labels={"Redis": "Labs"}) + with pytest.raises(exceptions.NoPermissionError): + await r.ts().info(1) + + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_acl_modules_category_commands(self, r_teardown): + username = "redis-py-user" + password = "pass-for-test-user" + + r = r_teardown(username) + await r.flushdb() + + # validate modules categories acl config + await r.acl_setuser( + username, + enabled=True, + reset=True, + passwords=[f"+{password}"], + categories=[ + "-all", + "+@search", + "+@json", + "+@bloom", + "+@cuckoo", + "+@topk", + "+@cms", + "+@timeseries", + "+@tdigest", + ], + keys=["*"], + ) + await r.ft().create_index((TextField("txt"),)) + await r.hset("doc1", mapping={"txt": "foo baz"}) + await r.hset("doc2", mapping={"txt": "foo bar"}) + + await r.auth(password, username) + + assert await r.ft().search(Query("foo ~bar")) + assert await r.ft().dropindex() + + assert await r.json().set("foo", Path.root_path(), "bar") + assert await r.json().get("foo") == "bar" + + assert await r.bf().create("bloom", 0.01, 1000) + assert await r.bf().info("bloom") + assert await r.cf().create("cuckoo", 1000) + assert await r.cms().initbydim("cmsDim", 100, 5) + assert await r.topk().reserve("topk", 5, 100, 5, 0.9) + assert await r.tdigest().create("to-tDigest", 10) + + assert await r.ts().create(1, labels={"Redis": "Labs"}) + assert await r.ts().info(1) + @pytest.mark.onlynoncluster async def test_client_list(self, r: redis.Redis): clients = await r.client_list() @@ -512,6 +684,33 @@ async def test_config_set(self, r: redis.Redis): assert await r.config_set("timeout", 0) assert (await r.config_get())["timeout"] == "0" + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_config_get_for_modules(self, r: redis.Redis): + search_module_configs = await r.config_get("search-*") + assert "search-timeout" in search_module_configs + + ts_module_configs = await r.config_get("ts-*") + assert "ts-num-threads" in ts_module_configs + + bf_module_configs = await r.config_get("bf-*") + assert "bf-initial-size" in bf_module_configs + + cf_module_configs = await r.config_get("cf-*") + assert "cf-max-iterations" in cf_module_configs + + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + async def test_config_set_for_search_module(self, r: redis.Redis): + search_timeout_initial = (await r.config_get())["search-timeout"] + search_timeout_new = int(search_timeout_initial) + 100 + + assert await r.config_set("search-timeout", search_timeout_new) + assert ( + int((await r.config_get("search-*"))["search-timeout"]) + == search_timeout_new + ) + @pytest.mark.onlynoncluster async def test_dbsize(self, r: redis.Redis): await r.set("a", "foo") diff --git a/tests/test_commands.py b/tests/test_commands.py index f83fe76aa9..6e6cbeef2b 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -18,6 +18,9 @@ parse_info, ) from redis.client import EMPTY_RESPONSE, NEVER_DECODE +from redis.commands.json.path import Path +from redis.commands.search.field import TextField +from redis.commands.search.query import Query from .conftest import ( _get_client, @@ -144,12 +147,65 @@ def test_acl_cat_no_category(self, r): assert isinstance(categories, list) assert "read" in categories or b"read" in categories + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_acl_cat_contain_modules_no_category(self, r): + modules_list = [ + "search", + "bloom", + "json", + "cuckoo", + "timeseries", + "cms", + "topk", + "tdigest", + ] + categories = r.acl_cat() + assert isinstance(categories, list) + for module_cat in modules_list: + assert module_cat in categories or module_cat.encode() in categories + @skip_if_server_version_lt("6.0.0") def test_acl_cat_with_category(self, r): commands = r.acl_cat("read") assert isinstance(commands, list) assert "get" in commands or b"get" in commands + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_acl_modules_cat_with_category(self, r): + search_commands = r.acl_cat("search") + assert isinstance(search_commands, list) + assert "FT.SEARCH" in search_commands or b"FT.SEARCH" in search_commands + + bloom_commands = r.acl_cat("bloom") + assert isinstance(bloom_commands, list) + assert "bf.add" in bloom_commands or b"bf.add" in bloom_commands + + json_commands = r.acl_cat("json") + assert isinstance(json_commands, list) + assert "json.get" in json_commands or b"json.get" in json_commands + + cuckoo_commands = r.acl_cat("cuckoo") + assert isinstance(cuckoo_commands, list) + assert "cf.insert" in cuckoo_commands or b"cf.insert" in cuckoo_commands + + cms_commands = r.acl_cat("cms") + assert isinstance(cms_commands, list) + assert "cms.query" in cms_commands or b"cms.query" in cms_commands + + topk_commands = r.acl_cat("topk") + assert isinstance(topk_commands, list) + assert "topk.list" in topk_commands or b"topk.list" in topk_commands + + tdigest_commands = r.acl_cat("tdigest") + assert isinstance(tdigest_commands, list) + assert "tdigest.rank" in tdigest_commands or b"tdigest.rank" in tdigest_commands + + timeseries_commands = r.acl_cat("timeseries") + assert isinstance(timeseries_commands, list) + assert "ts.range" in timeseries_commands or b"ts.range" in timeseries_commands + @skip_if_server_version_lt("7.0.0") @skip_if_redis_enterprise() def test_acl_dryrun(self, r, request): @@ -458,6 +514,123 @@ def test_acl_whoami(self, r): username = r.acl_whoami() assert isinstance(username, (str, bytes)) + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_acl_modules_commands(self, r, request): + default_username = "default" + username = "redis-py-user" + password = "pass-for-test-user" + + def teardown(): + r.auth("", default_username) + r.acl_deluser(username) + + request.addfinalizer(teardown) + + r.ft().create_index((TextField("txt"),)) + r.hset("doc1", mapping={"txt": "foo baz"}) + r.hset("doc2", mapping={"txt": "foo bar"}) + + r.acl_setuser( + username, + enabled=True, + reset=True, + passwords=[f"+{password}"], + categories=["-all"], + commands=[ + "+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", + ], + keys=["*"], + ) + r.auth(password, username) + + assert r.ft().search(Query("foo ~bar")) + with pytest.raises(exceptions.NoPermissionError): + r.ft().dropindex() + + r.json().set("foo", Path.root_path(), "bar") + assert r.json().get("foo") == "bar" + with pytest.raises(exceptions.NoPermissionError): + r.json().clear("foo") + + assert r.bf().create("bloom", 0.01, 1000) + assert r.cf().create("cuckoo", 1000) + assert r.cms().initbydim("cmsDim", 100, 5) + assert r.topk().reserve("topk", 5, 100, 5, 0.9) + assert r.tdigest().create("to-tDigest", 10) + with pytest.raises(exceptions.NoPermissionError): + r.bf().info("bloom") + + assert r.ts().create(1, labels={"Redis": "Labs"}) + with pytest.raises(exceptions.NoPermissionError): + r.ts().info(1) + + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_acl_modules_category_commands(self, r, request): + default_username = "default" + username = "redis-py-user" + password = "pass-for-test-user" + + def teardown(): + r.auth("", default_username) + r.acl_deluser(username) + + request.addfinalizer(teardown) + + # validate modules categories acl config + r.acl_setuser( + username, + enabled=True, + reset=True, + passwords=[f"+{password}"], + categories=[ + "-all", + "+@search", + "+@json", + "+@bloom", + "+@cuckoo", + "+@topk", + "+@cms", + "+@timeseries", + "+@tdigest", + ], + keys=["*"], + ) + r.ft().create_index((TextField("txt"),)) + r.hset("doc1", mapping={"txt": "foo baz"}) + r.hset("doc2", mapping={"txt": "foo bar"}) + + r.auth(password, username) + + assert r.ft().search(Query("foo ~bar")) + assert r.ft().dropindex() + + assert r.json().set("foo", Path.root_path(), "bar") + assert r.json().get("foo") == "bar" + + assert r.bf().create("bloom", 0.01, 1000) + assert r.bf().info("bloom") + assert r.cf().create("cuckoo", 1000) + assert r.cms().initbydim("cmsDim", 100, 5) + assert r.topk().reserve("topk", 5, 100, 5, 0.9) + assert r.tdigest().create("to-tDigest", 10) + + assert r.ts().create(1, labels={"Redis": "Labs"}) + assert r.ts().info(1) + @pytest.mark.onlynoncluster def test_client_list(self, r): clients = r.client_list() @@ -824,6 +997,30 @@ def test_config_set_multi_params(self, r: redis.Redis): assert r.config_get()["timeout"] == "0" assert r.config_get()["maxmemory"] == "0" + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_config_get_for_modules(self, r: redis.Redis): + search_module_configs = r.config_get("search-*") + assert "search-timeout" in search_module_configs + + ts_module_configs = r.config_get("ts-*") + assert "ts-num-threads" in ts_module_configs + + bf_module_configs = r.config_get("bf-*") + assert "bf-initial-size" in bf_module_configs + + cf_module_configs = r.config_get("cf-*") + assert "cf-max-iterations" in cf_module_configs + + @pytest.mark.redismod + @skip_if_server_version_lt("7.9.0") + def test_config_set_for_search_module(self, r: redis.Redis): + search_timeout_initial = r.config_get()["search-timeout"] + search_timeout_new = int(search_timeout_initial) + 100 + + assert r.config_set("search-timeout", search_timeout_new) + assert int(r.config_get("search-*")["search-timeout"]) == search_timeout_new + @skip_if_server_version_lt("6.0.0") @skip_if_redis_enterprise() def test_failover(self, r):