From 532ab2ce2868b8521d2586646a3b209502143214 Mon Sep 17 00:00:00 2001
From: Mikhail Koviazin <mikhail.koviazin@aiven.io>
Date: Tue, 11 Jun 2024 12:20:43 +0300
Subject: [PATCH 1/5] Revert "core: remove mentions to older Redis from error
 and docstring"

This reverts commit ebbc1d45038e2861a0dbe004879a315e4035c85c.
It also changes the mentions of "valkey 6.2" and "Redis 6.2" to "server 6.2"

Signed-off-by: Mikhail Koviazin <mikhail.koviazin@aiven.io>
---
 valkey/commands/core.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/valkey/commands/core.py b/valkey/commands/core.py
index bdf41c77..cd071c31 100644
--- a/valkey/commands/core.py
+++ b/valkey/commands/core.py
@@ -3855,8 +3855,8 @@ def xpending_range(
 
         name: name of the stream.
         groupname: name of the consumer group.
-        idle: filter entries by their idle-time, given
-        in milliseconds (optional).
+        idle: available from  version 6.2. filter entries by their
+        idle-time, given in milliseconds (optional).
         min: minimum stream ID.
         max: maximum stream ID.
         count: number of messages to return
@@ -5364,9 +5364,12 @@ def script_flush(
         For more information see  https://valkey.io/commands/script-flush
         """
 
+        # Server pre 6 had no sync_type.
         if sync_type not in ["SYNC", "ASYNC", None]:
             raise DataError(
-                "SCRIPT FLUSH defaults to SYNC in valkey, or accepts SYNC/ASYNC."
+                "SCRIPT FLUSH defaults to SYNC in server version > 6.2, or "
+                "accepts SYNC/ASYNC. For older versions, "
+                "of valkey leave as None."
             )
         if sync_type is None:
             pieces = []

From 7a0a634eca941025362e5b8d7d1ff529d0642787 Mon Sep 17 00:00:00 2001
From: Mikhail Koviazin <mikhail.koviazin@aiven.io>
Date: Tue, 11 Jun 2024 12:23:23 +0300
Subject: [PATCH 2/5] Revert "remove older redis-py versions from deprecated
 warnings"

This reverts commit d4074a956b8e8304f55c31d09d869c9454f37533.

Signed-off-by: Mikhail Koviazin <mikhail.koviazin@aiven.io>
---
 valkey/asyncio/client.py           | 12 +++++++-----
 valkey/asyncio/cluster.py          |  2 +-
 valkey/commands/bf/commands.py     |  2 +-
 valkey/commands/json/commands.py   | 14 ++++++++++----
 valkey/commands/search/commands.py |  8 ++++++--
 5 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/valkey/asyncio/client.py b/valkey/asyncio/client.py
index 9af8d087..960de835 100644
--- a/valkey/asyncio/client.py
+++ b/valkey/asyncio/client.py
@@ -171,7 +171,8 @@ class initializer. In the case of conflicting arguments, querystring
         if auto_close_connection_pool is not None:
             warnings.warn(
                 DeprecationWarning(
-                    '"auto_close_connection_pool" is deprecated. '
+                    '"auto_close_connection_pool" is deprecated '
+                    "since version 5.0.1. "
                     "Please create a ConnectionPool explicitly and "
                     "provide to the Valkey() constructor instead."
                 )
@@ -258,7 +259,8 @@ def __init__(
         if auto_close_connection_pool is not None:
             warnings.warn(
                 DeprecationWarning(
-                    '"auto_close_connection_pool" is deprecated. '
+                    '"auto_close_connection_pool" is deprecated '
+                    "since version 5.0.1. "
                     "Please create a ConnectionPool explicitly and "
                     "provide to the Valkey() constructor instead."
                 )
@@ -589,7 +591,7 @@ async def aclose(self, close_connection_pool: Optional[bool] = None) -> None:
         ):
             await self.connection_pool.disconnect()
 
-    @deprecated_function(reason="Use aclose() instead", name="close")
+    @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close")
     async def close(self, close_connection_pool: Optional[bool] = None) -> None:
         """
         Alias for aclose(), for backwards compatibility
@@ -853,12 +855,12 @@ async def aclose(self):
             self.patterns = {}
             self.pending_unsubscribe_patterns = set()
 
-    @deprecated_function(reason="Use aclose() instead", name="close")
+    @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="close")
     async def close(self) -> None:
         """Alias for aclose(), for backwards compatibility"""
         await self.aclose()
 
-    @deprecated_function(reason="Use aclose() instead", name="reset")
+    @deprecated_function(version="5.0.1", reason="Use aclose() instead", name="reset")
     async def reset(self) -> None:
         """Alias for aclose(), for backwards compatibility"""
         await self.aclose()
diff --git a/valkey/asyncio/cluster.py b/valkey/asyncio/cluster.py
index 5ee74621..4e7e3580 100644
--- a/valkey/asyncio/cluster.py
+++ b/valkey/asyncio/cluster.py
@@ -446,7 +446,7 @@ async def aclose(self) -> None:
                     await self.nodes_manager.aclose()
                     await self.nodes_manager.aclose("startup_nodes")
 
-    @deprecated_function(reason="Use aclose() instead", name="close")
+    @deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close")
     async def close(self) -> None:
         """alias for aclose() for backwards compatibility"""
         await self.aclose()
diff --git a/valkey/commands/bf/commands.py b/valkey/commands/bf/commands.py
index b8d192ff..0e70cd3a 100644
--- a/valkey/commands/bf/commands.py
+++ b/valkey/commands/bf/commands.py
@@ -332,7 +332,7 @@ def query(self, key, *items):
         """  # noqa
         return self.execute_command(TOPK_QUERY, key, *items)
 
-    @deprecated_function(reason="deprecated since valkeybloom 2.4.0")
+    @deprecated_function(version="4.4.0", reason="deprecated since valkeybloom 2.4.0")
     def count(self, key, *items):
         """
         Return count for one `item` or more from `key`.
diff --git a/valkey/commands/json/commands.py b/valkey/commands/json/commands.py
index 0d493124..3b7ee09a 100644
--- a/valkey/commands/json/commands.py
+++ b/valkey/commands/json/commands.py
@@ -141,7 +141,7 @@ def numincrby(self, name: str, path: str, number: int) -> str:
             "JSON.NUMINCRBY", name, str(path), self._encode(number)
         )
 
-    @deprecated_function(reason="deprecated since redisjson 1.0.0")
+    @deprecated_function(version="4.0.0", reason="deprecated since redisjson 1.0.0")
     def nummultby(self, name: str, path: str, number: int) -> str:
         """Multiply the numeric (integer or floating point) JSON value under
         ``path`` at key ``name`` with the provided ``number``.
@@ -412,14 +412,20 @@ def debug(
             pieces.append(str(path))
         return self.execute_command("JSON.DEBUG", *pieces)
 
-    @deprecated_function(reason="redisjson-py supported this, call get directly.")
+    @deprecated_function(
+        version="4.0.0", reason="redisjson-py supported this, call get directly."
+    )
     def jsonget(self, *args, **kwargs):
         return self.get(*args, **kwargs)
 
-    @deprecated_function(reason="redisjson-py supported this, call get directly.")
+    @deprecated_function(
+        version="4.0.0", reason="redisjson-py supported this, call get directly."
+    )
     def jsonmget(self, *args, **kwargs):
         return self.mget(*args, **kwargs)
 
-    @deprecated_function(reason="redisjson-py supported this, call get directly.")
+    @deprecated_function(
+        version="4.0.0", reason="redisjson-py supported this, call get directly."
+    )
     def jsonset(self, *args, **kwargs):
         return self.set(*args, **kwargs)
diff --git a/valkey/commands/search/commands.py b/valkey/commands/search/commands.py
index bf97a75a..e16fc9d7 100644
--- a/valkey/commands/search/commands.py
+++ b/valkey/commands/search/commands.py
@@ -317,7 +317,9 @@ def _add_document_hash(
 
         return self.execute_command(*args)
 
-    @deprecated_function(reason="deprecated since redisearch 2.0, call hset instead")
+    @deprecated_function(
+        version="2.0.0", reason="deprecated since redisearch 2.0, call hset instead"
+    )
     def add_document(
         self,
         doc_id: str,
@@ -371,7 +373,9 @@ def add_document(
             **fields,
         )
 
-    @deprecated_function(reason="deprecated since valkeyearch 2.0, call hset instead")
+    @deprecated_function(
+        version="2.0.0", reason="deprecated since valkeyearch 2.0, call hset instead"
+    )
     def add_document_hash(self, doc_id, score=1.0, language=None, replace=False):
         """
         Add a hash document to the index.

From 366314bf694d45f79aa14540ffb16737b7b26b14 Mon Sep 17 00:00:00 2001
From: Mikhail Koviazin <mikhail.koviazin@aiven.io>
Date: Tue, 11 Jun 2024 12:25:45 +0300
Subject: [PATCH 3/5] Revert "connection: remove a workaround for obsolete
 Redis version"

This reverts commit 506b78942551b6eb67992bb9877b0e41576c61e4.
Also mentions of "Valkey < 6.0.0" are fixed.

Signed-off-by: Mikhail Koviazin <mikhail.koviazin@aiven.io>
---
 valkey/asyncio/connection.py | 12 +++++++++++-
 valkey/connection.py         | 12 +++++++++++-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/valkey/asyncio/connection.py b/valkey/asyncio/connection.py
index 908f0dd9..77b329a8 100644
--- a/valkey/asyncio/connection.py
+++ b/valkey/asyncio/connection.py
@@ -40,6 +40,7 @@
 from valkey.credentials import CredentialProvider, UsernamePasswordCredentialProvider
 from valkey.exceptions import (
     AuthenticationError,
+    AuthenticationWrongNumberOfArgsError,
     ConnectionError,
     DataError,
     ResponseError,
@@ -381,7 +382,16 @@ async def on_connect(self) -> None:
         # to check the health prior to the AUTH
         elif auth_args:
             await self.send_command("AUTH", *auth_args, check_health=False)
-            auth_response = await self.read_response()
+
+            try:
+                auth_response = await self.read_response()
+            except AuthenticationWrongNumberOfArgsError:
+                # a username and password were specified but the server seems
+                # to be < 6.0.0 which expects a single password arg.
+                # retry auth with just the password.
+                # https://github.com/andymccurdy/redis-py/issues/1274
+                await self.send_command("AUTH", auth_args[-1], check_health=False)
+                auth_response = await self.read_response()
 
             if str_if_bytes(auth_response) != "OK":
                 raise AuthenticationError("Invalid Username or Password")
diff --git a/valkey/connection.py b/valkey/connection.py
index 57a9836e..29d3fbb0 100644
--- a/valkey/connection.py
+++ b/valkey/connection.py
@@ -24,6 +24,7 @@
 from .credentials import CredentialProvider, UsernamePasswordCredentialProvider
 from .exceptions import (
     AuthenticationError,
+    AuthenticationWrongNumberOfArgsError,
     ChildDeadlockedError,
     ConnectionError,
     DataError,
@@ -375,7 +376,16 @@ def on_connect(self):
             # avoid checking health here -- PING will fail if we try
             # to check the health prior to the AUTH
             self.send_command("AUTH", *auth_args, check_health=False)
-            auth_response = self.read_response()
+
+            try:
+                auth_response = self.read_response()
+            except AuthenticationWrongNumberOfArgsError:
+                # a username and password were specified but the server seems
+                # to be < 6.0.0 which expects a single password arg.
+                # retry auth with just the password.
+                # https://github.com/andymccurdy/redis-py/issues/1274
+                self.send_command("AUTH", auth_args[-1], check_health=False)
+                auth_response = self.read_response()
 
             if str_if_bytes(auth_response) != "OK":
                 raise AuthenticationError("Invalid Username or Password")

From 5dc5a52c1bb8ca1b429c922af59ec5dcd1f3632b Mon Sep 17 00:00:00 2001
From: Mikhail Koviazin <mikhail.koviazin@aiven.io>
Date: Tue, 11 Jun 2024 12:32:47 +0300
Subject: [PATCH 4/5] Revert "base: remove the snippet specific to Redis < 6.0"

This reverts commit a0e20ead451f6e3ca8fb5931c79013d255d5c71e.
Additionally, it replaces "Redis" with "Server" in comments.

Signed-off-by: Mikhail Koviazin <mikhail.koviazin@aiven.io>
---
 tests/test_asyncio/test_connection_pool.py |  6 +-----
 tests/test_connection_pool.py              |  7 +++++++
 valkey/_parsers/base.py                    | 11 +++++++----
 3 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/tests/test_asyncio/test_connection_pool.py b/tests/test_asyncio/test_connection_pool.py
index 8d384bf5..e63f3f51 100644
--- a/tests/test_asyncio/test_connection_pool.py
+++ b/tests/test_asyncio/test_connection_pool.py
@@ -666,11 +666,7 @@ async def test_connect_no_auth_supplied_when_required(self, r):
         """
         with pytest.raises(valkey.AuthenticationError):
             await r.execute_command(
-                "DEBUG",
-                "ERROR",
-                "ERR AUTH <password> called without any password "
-                "configured for the default user. Are you sure "
-                "your configuration is correct?",
+                "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set"
             )
 
     async def test_connect_invalid_password_supplied(self, r):
diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py
index 5cb1dba4..ede90c15 100644
--- a/tests/test_connection_pool.py
+++ b/tests/test_connection_pool.py
@@ -583,6 +583,13 @@ def test_connect_no_auth_configured(self, r):
         AuthenticationError should be raised when the server is not configured with auth
         but credentials are supplied by the user.
         """
+        # Server < 6
+        with pytest.raises(valkey.AuthenticationError):
+            r.execute_command(
+                "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set"
+            )
+
+        # Server >= 6
         with pytest.raises(valkey.AuthenticationError):
             r.execute_command(
                 "DEBUG",
diff --git a/valkey/_parsers/base.py b/valkey/_parsers/base.py
index a123acbc..0f9b10b1 100644
--- a/valkey/_parsers/base.py
+++ b/valkey/_parsers/base.py
@@ -35,11 +35,14 @@
     "types, can't unload"
 )
 # user send an AUTH cmd to a server without authorization configured
-NO_AUTH_SET_ERROR = (
+NO_AUTH_SET_ERROR = {
+    # Server >= 6.0
     "AUTH <password> called without any password "
     "configured for the default user. Are you sure "
-    "your configuration is correct?"
-)
+    "your configuration is correct?": AuthenticationError,
+    # Server < 6.0
+    "Client sent AUTH, but no password is set": AuthenticationError,
+}
 
 
 class BaseParser(ABC):
@@ -59,7 +62,7 @@ class BaseParser(ABC):
             MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError,
             NO_SUCH_MODULE_ERROR: ModuleError,
             MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
-            NO_AUTH_SET_ERROR: AuthenticationError,
+            **NO_AUTH_SET_ERROR,
         },
         "OOM": OutOfMemoryError,
         "WRONGPASS": AuthenticationError,

From 574d6b3e9d3829492a0a2e11e1af7f3b34e9ba9a Mon Sep 17 00:00:00 2001
From: Mikhail Koviazin <mikhail.koviazin@aiven.io>
Date: Tue, 11 Jun 2024 12:34:55 +0300
Subject: [PATCH 5/5] Revert "remove obsolete tests" and "drop
 skip_if_server_version_lt"

This commit reverts b0fa7da and c175433 and effectively returns the
tests for the older server versions.

Signed-off-by: Mikhail Koviazin <mikhail.koviazin@aiven.io>
---
 tests/test_asyncio/test_cluster.py         |  83 ++++++
 tests/test_asyncio/test_commands.py        | 145 ++++++++++
 tests/test_asyncio/test_connection.py      |   2 +
 tests/test_asyncio/test_connection_pool.py |  10 +
 tests/test_asyncio/test_pipeline.py        |   2 +
 tests/test_asyncio/test_pubsub.py          |   8 +-
 tests/test_asyncio/test_scripting.py       |   2 +
 tests/test_cluster.py                      |  51 ++++
 tests/test_command_parser.py               |   3 +-
 tests/test_commands.py                     | 306 +++++++++++++++++++++
 tests/test_connection.py                   |   2 +
 tests/test_connection_pool.py              |  11 +-
 tests/test_function.py                     |   3 +-
 tests/test_pipeline.py                     |   3 +-
 tests/test_pubsub.py                       |  30 +-
 tests/test_scripting.py                    |   4 +
 16 files changed, 659 insertions(+), 6 deletions(-)

diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py
index 6b477e26..6e85f16c 100644
--- a/tests/test_asyncio/test_cluster.py
+++ b/tests/test_asyncio/test_cluster.py
@@ -12,6 +12,8 @@
 from tests.conftest import (
     assert_resp_response,
     is_resp2_connection,
+    skip_if_server_version_gte,
+    skip_if_server_version_lt,
     skip_unless_arch_bits,
 )
 from valkey._parsers import AsyncCommandsParser
@@ -1050,6 +1052,7 @@ async def test_cluster_myid(self, r: ValkeyCluster) -> None:
         myid = await r.cluster_myid(node)
         assert len(myid) == 40
 
+    @skip_if_server_version_lt("7.2.0")
     async def test_cluster_myshardid(self, r: ValkeyCluster) -> None:
         node = r.get_random_node()
         myshardid = await r.cluster_myshardid(node)
@@ -1068,6 +1071,7 @@ async def test_cluster_addslots(self, r: ValkeyCluster) -> None:
         mock_node_resp(node, "OK")
         assert await r.cluster_addslots(node, 1, 2, 3) is True
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_cluster_addslotsrange(self, r: ValkeyCluster):
         node = r.get_random_node()
         mock_node_resp(node, "OK")
@@ -1099,6 +1103,7 @@ async def test_cluster_delslots(self) -> None:
 
         await r.aclose()
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_cluster_delslotsrange(self):
         r = await get_mocked_valkey_client(host=default_host, port=default_port)
         mock_all_nodes_resp(r, "OK")
@@ -1275,6 +1280,7 @@ async def test_cluster_replicas(self, r: ValkeyCluster) -> None:
             == "r4xfga22229cf3c652b6fca0d09ff69f3e0d4d"
         )
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_cluster_links(self, r: ValkeyCluster):
         node = r.get_random_node()
         res = await r.cluster_links(node)
@@ -1405,13 +1411,16 @@ async def test_time(self, r: ValkeyCluster) -> None:
         assert isinstance(t[0], int)
         assert isinstance(t[1], int)
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_usage(self, r: ValkeyCluster) -> None:
         await r.set("foo", "bar")
         assert isinstance(await r.memory_usage("foo"), int)
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_malloc_stats(self, r: ValkeyCluster) -> None:
         assert await r.memory_malloc_stats()
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_stats(self, r: ValkeyCluster) -> None:
         # put a key into the current db to make sure that "db.<current-db>"
         # has data
@@ -1423,10 +1432,12 @@ async def test_memory_stats(self, r: ValkeyCluster) -> None:
             if key.startswith("db."):
                 assert isinstance(value, dict)
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_help(self, r: ValkeyCluster) -> None:
         with pytest.raises(NotImplementedError):
             await r.memory_help()
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_doctor(self, r: ValkeyCluster) -> None:
         with pytest.raises(NotImplementedError):
             await r.memory_doctor()
@@ -1439,6 +1450,7 @@ async def test_cluster_echo(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         assert await r.echo("foo bar", target_nodes=node) == b"foo bar"
 
+    @skip_if_server_version_lt("1.0.0")
     async def test_debug_segfault(self, r: ValkeyCluster) -> None:
         with pytest.raises(NotImplementedError):
             await r.debug_segfault()
@@ -1456,12 +1468,14 @@ async def test_config_resetstat(self, r: ValkeyCluster) -> None:
         )
         assert reset_commands_processed < prior_commands_processed
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_client_trackinginfo(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         res = await r.client_trackinginfo(target_nodes=node)
         assert len(res) > 2
         assert "prefixes" in res or b"prefixes" in res
 
+    @skip_if_server_version_lt("2.9.50")
     async def test_client_pause(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         assert await r.client_pause(1, target_nodes=node)
@@ -1469,13 +1483,16 @@ async def test_client_pause(self, r: ValkeyCluster) -> None:
         with pytest.raises(ValkeyError):
             await r.client_pause(timeout="not an integer", target_nodes=node)
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_client_unpause(self, r: ValkeyCluster) -> None:
         assert await r.client_unpause()
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_client_id(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         assert await r.client_id(target_nodes=node) > 0
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_client_unblock(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         myid = await r.client_id(target_nodes=node)
@@ -1483,17 +1500,20 @@ async def test_client_unblock(self, r: ValkeyCluster) -> None:
         assert not await r.client_unblock(myid, error=True, target_nodes=node)
         assert not await r.client_unblock(myid, error=False, target_nodes=node)
 
+    @skip_if_server_version_lt("6.0.0")
     async def test_client_getredir(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         assert isinstance(await r.client_getredir(target_nodes=node), int)
         assert await r.client_getredir(target_nodes=node) == -1
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_client_info(self, r: ValkeyCluster) -> None:
         node = r.get_primaries()[0]
         info = await r.client_info(target_nodes=node)
         assert isinstance(info, dict)
         assert "addr" in info
 
+    @skip_if_server_version_lt("2.6.9")
     async def test_client_kill(
         self, r: ValkeyCluster, create_valkey: Callable[..., ValkeyCluster]
     ) -> None:
@@ -1521,11 +1541,13 @@ async def test_client_kill(
         assert clients[0].get("name") == "valkey-py-c1"
         await r2.aclose()
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_cluster_bitop_not_empty_string(self, r: ValkeyCluster) -> None:
         await r.set("{foo}a", "")
         await r.bitop("not", "{foo}r", "{foo}a")
         assert await r.get("{foo}r") is None
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_cluster_bitop_not(self, r: ValkeyCluster) -> None:
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -1533,6 +1555,7 @@ async def test_cluster_bitop_not(self, r: ValkeyCluster) -> None:
         await r.bitop("not", "{foo}r", "{foo}a")
         assert int(binascii.hexlify(await r.get("{foo}r")), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_cluster_bitop_not_in_place(self, r: ValkeyCluster) -> None:
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -1540,6 +1563,7 @@ async def test_cluster_bitop_not_in_place(self, r: ValkeyCluster) -> None:
         await r.bitop("not", "{foo}a", "{foo}a")
         assert int(binascii.hexlify(await r.get("{foo}a")), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_cluster_bitop_single_string(self, r: ValkeyCluster) -> None:
         test_str = b"\x01\x02\xFF"
         await r.set("{foo}a", test_str)
@@ -1550,6 +1574,7 @@ async def test_cluster_bitop_single_string(self, r: ValkeyCluster) -> None:
         assert await r.get("{foo}res2") == test_str
         assert await r.get("{foo}res3") == test_str
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_cluster_bitop_string_operands(self, r: ValkeyCluster) -> None:
         await r.set("{foo}a", b"\x01\x02\xFF\xFF")
         await r.set("{foo}b", b"\x01\x02\xFF")
@@ -1560,6 +1585,7 @@ async def test_cluster_bitop_string_operands(self, r: ValkeyCluster) -> None:
         assert int(binascii.hexlify(await r.get("{foo}res2")), 16) == 0x0102FFFF
         assert int(binascii.hexlify(await r.get("{foo}res3")), 16) == 0x000000FF
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_copy(self, r: ValkeyCluster) -> None:
         assert await r.copy("{foo}a", "{foo}b") == 0
         await r.set("{foo}a", "bar")
@@ -1567,17 +1593,20 @@ async def test_cluster_copy(self, r: ValkeyCluster) -> None:
         assert await r.get("{foo}a") == b"bar"
         assert await r.get("{foo}b") == b"bar"
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_copy_and_replace(self, r: ValkeyCluster) -> None:
         await r.set("{foo}a", "foo1")
         await r.set("{foo}b", "foo2")
         assert await r.copy("{foo}a", "{foo}b") == 0
         assert await r.copy("{foo}a", "{foo}b", replace=True) == 1
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_lmove(self, r: ValkeyCluster) -> None:
         await r.rpush("{foo}a", "one", "two", "three", "four")
         assert await r.lmove("{foo}a", "{foo}b")
         assert await r.lmove("{foo}a", "{foo}b", "right", "left")
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_blmove(self, r: ValkeyCluster) -> None:
         await r.rpush("{foo}a", "one", "two", "three", "four")
         assert await r.blmove("{foo}a", "{foo}b", 5)
@@ -1738,6 +1767,7 @@ async def test_cluster_sunionstore(self, r: ValkeyCluster) -> None:
         assert await r.sunionstore("{foo}c", "{foo}a", "{foo}b") == 3
         assert await r.smembers("{foo}c") == {b"1", b"2", b"3"}
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_zdiff(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         await r.zadd("{foo}b", {"a1": 1, "a2": 2})
@@ -1745,6 +1775,7 @@ async def test_cluster_zdiff(self, r: ValkeyCluster) -> None:
         response = await r.zdiff(["{foo}a", "{foo}b"], withscores=True)
         assert_resp_response(r, response, [b"a3", b"3"], [[b"a3", 3.0]])
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_zdiffstore(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         await r.zadd("{foo}b", {"a1": 1, "a2": 2})
@@ -1753,6 +1784,7 @@ async def test_cluster_zdiffstore(self, r: ValkeyCluster) -> None:
         response = await r.zrange("{foo}out", 0, -1, withscores=True)
         assert_resp_response(r, response, [(b"a3", 3.0)], [[b"a3", 3.0]])
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_zinter(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 1})
         await r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2})
@@ -1848,6 +1880,7 @@ async def test_cluster_zinterstore_with_weight(self, r: ValkeyCluster) -> None:
             [[b"a3", 20.0], [b"a1", 23.0]],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     async def test_cluster_bzpopmax(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2})
         await r.zadd("{foo}b", {"b1": 10, "b2": 20})
@@ -1884,6 +1917,7 @@ async def test_cluster_bzpopmax(self, r: ValkeyCluster) -> None:
             [b"{foo}c", b"c1", 100],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     async def test_cluster_bzpopmin(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2})
         await r.zadd("{foo}b", {"b1": 10, "b2": 20})
@@ -1920,6 +1954,7 @@ async def test_cluster_bzpopmin(self, r: ValkeyCluster) -> None:
             [b"{foo}c", b"c1", 100],
         )
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_zrangestore(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         assert await r.zrangestore("{foo}b", "{foo}a", 0, 1)
@@ -1946,6 +1981,7 @@ async def test_cluster_zrangestore(self, r: ValkeyCluster) -> None:
         )
         assert await r.zrange("{foo}b", 0, -1) == [b"a2"]
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_zunion(self, r: ValkeyCluster) -> None:
         await r.zadd("{foo}a", {"a1": 1, "a2": 1, "a3": 1})
         await r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2})
@@ -2049,6 +2085,7 @@ async def test_cluster_zunionstore_with_weight(self, r: ValkeyCluster) -> None:
             [[b"a2", 5.0], [b"a4", 12.0], [b"a3", 20.0], [b"a1", 23.0]],
         )
 
+    @skip_if_server_version_lt("2.8.9")
     async def test_cluster_pfcount(self, r: ValkeyCluster) -> None:
         members = {b"1", b"2", b"3"}
         await r.pfadd("{foo}a", *members)
@@ -2058,6 +2095,7 @@ async def test_cluster_pfcount(self, r: ValkeyCluster) -> None:
         assert await r.pfcount("{foo}b") == len(members_b)
         assert await r.pfcount("{foo}a", "{foo}b") == len(members_b.union(members))
 
+    @skip_if_server_version_lt("2.8.9")
     async def test_cluster_pfmerge(self, r: ValkeyCluster) -> None:
         mema = {b"1", b"2", b"3"}
         memb = {b"2", b"3", b"4"}
@@ -2076,6 +2114,7 @@ async def test_cluster_sort_store(self, r: ValkeyCluster) -> None:
         assert await r.lrange("{foo}sorted_values", 0, -1) == [b"1", b"2", b"3"]
 
     # GEO COMMANDS
+    @skip_if_server_version_lt("6.2.0")
     async def test_cluster_geosearchstore(self, r: ValkeyCluster) -> None:
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2094,6 +2133,7 @@ async def test_cluster_geosearchstore(self, r: ValkeyCluster) -> None:
         assert await r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("6.2.0")
     async def test_geosearchstore_dist(self, r: ValkeyCluster) -> None:
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2113,6 +2153,7 @@ async def test_geosearchstore_dist(self, r: ValkeyCluster) -> None:
         # instead of save the geo score, the distance is saved.
         assert await r.zscore("{foo}places_barcelona", "place1") == 88.05060698409301
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_cluster_georadius_store(self, r: ValkeyCluster) -> None:
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2127,6 +2168,7 @@ async def test_cluster_georadius_store(self, r: ValkeyCluster) -> None:
         assert await r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     async def test_cluster_georadius_store_dist(self, r: ValkeyCluster) -> None:
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2159,6 +2201,7 @@ async def test_cluster_keys(self, r: ValkeyCluster) -> None:
         assert set(await r.keys(pattern="test*", target_nodes="primaries")) == keys
 
     # SCAN COMMANDS
+    @skip_if_server_version_lt("2.8.0")
     async def test_cluster_scan(self, r: ValkeyCluster) -> None:
         await r.set("a", 1)
         await r.set("b", 2)
@@ -2177,6 +2220,7 @@ async def test_cluster_scan(self, r: ValkeyCluster) -> None:
             assert sorted(cursors.keys()) == sorted(node.name for node in nodes)
             assert all(cursor == 0 for cursor in cursors.values())
 
+    @skip_if_server_version_lt("6.0.0")
     async def test_cluster_scan_type(self, r: ValkeyCluster) -> None:
         await r.sadd("a-set", 1)
         await r.sadd("b-set", 1)
@@ -2199,6 +2243,7 @@ async def test_cluster_scan_type(self, r: ValkeyCluster) -> None:
             assert sorted(cursors.keys()) == sorted(node.name for node in nodes)
             assert all(cursor == 0 for cursor in cursors.values())
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_cluster_scan_iter(self, r: ValkeyCluster) -> None:
         keys_all = []
         keys_1 = []
@@ -2227,6 +2272,7 @@ async def test_cluster_randomkey(self, r: ValkeyCluster) -> None:
             await r.set(key, 1)
         assert await r.randomkey(target_nodes=node) in (b"{foo}a", b"{foo}b", b"{foo}c")
 
+    @skip_if_server_version_lt("6.0.0")
     async def test_acl_log(
         self, r: ValkeyCluster, create_valkey: Callable[..., ValkeyCluster]
     ) -> None:
@@ -2728,6 +2774,43 @@ async def test_asking_error(self, r: ValkeyCluster) -> None:
             assert ask_node._free.pop().read_response.await_count
             assert res == ["MOCK_OK"]
 
+    @skip_if_server_version_gte("7.0.0")
+    async def test_moved_redirection_on_slave_with_default(
+        self, r: ValkeyCluster
+    ) -> None:
+        """Test MovedError handling."""
+        key = "foo"
+        await r.set("foo", "bar")
+        # set read_from_replicas to True
+        r.read_from_replicas = True
+        primary = r.get_node_from_key(key, False)
+        moved_error = f"{r.keyslot(key)} {primary.host}:{primary.port}"
+
+        parse_response_orig = primary.parse_response
+        with mock.patch.object(
+            ClusterNode, "parse_response", autospec=True
+        ) as parse_response_mock:
+
+            async def parse_response(
+                self, connection: Connection, command: str, **kwargs: Any
+            ) -> Any:
+                if (
+                    command == "GET"
+                    and self.host != primary.host
+                    and self.port != primary.port
+                ):
+                    raise MovedError(moved_error)
+
+                return await parse_response_orig(connection, command, **kwargs)
+
+            parse_response_mock.side_effect = parse_response
+
+            async with r.pipeline() as readwrite_pipe:
+                assert r.reinitialize_counter == 0
+                readwrite_pipe.get(key).get(key)
+                assert r.reinitialize_counter == 0
+                assert await readwrite_pipe.execute() == [b"bar", b"bar"]
+
     async def test_readonly_pipeline_from_readonly_client(
         self, r: ValkeyCluster
     ) -> None:
diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py
index 21b4077c..3b570c90 100644
--- a/tests/test_asyncio/test_commands.py
+++ b/tests/test_asyncio/test_commands.py
@@ -16,6 +16,7 @@
     assert_resp_response,
     assert_resp_response_in,
     is_resp2_connection,
+    skip_if_server_version_gte,
     skip_if_server_version_lt,
     skip_unless_arch_bits,
 )
@@ -33,6 +34,8 @@
 else:
     from async_timeout import timeout as async_timeout
 
+VALKEY_6_VERSION = "5.9.0"
+
 
 @pytest_asyncio.fixture()
 async def r_teardown(r: valkey.Valkey):
@@ -106,16 +109,19 @@ async def test_command_on_invalid_key_type(self, r: valkey.Valkey):
             await r.get("a")
 
     # SERVER INFORMATION
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_cat_no_category(self, r: valkey.Valkey):
         categories = await r.acl_cat()
         assert isinstance(categories, list)
         assert "read" in categories or b"read" in categories
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_cat_with_category(self, r: valkey.Valkey):
         commands = await r.acl_cat("read")
         assert isinstance(commands, list)
         assert "get" in commands or b"get" in commands
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_deluser(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -124,10 +130,12 @@ async def test_acl_deluser(self, r_teardown):
         assert await r.acl_setuser(username, enabled=False, reset=True)
         assert await r.acl_deluser(username) == 1
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_genpass(self, r: valkey.Valkey):
         password = await r.acl_genpass()
         assert isinstance(password, (str, bytes))
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_acl_getuser_setuser(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -227,6 +235,7 @@ async def test_acl_getuser_setuser(self, r_teardown):
         )
         assert len((await r.acl_getuser(username))["passwords"]) == 1
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_list(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -235,6 +244,7 @@ async def test_acl_list(self, r_teardown):
         users = await r.acl_list()
         assert len(users) == len(start) + 1
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     @pytest.mark.onlynoncluster
     async def test_acl_log(self, r_teardown, create_valkey):
         username = "valkey-py-user"
@@ -271,6 +281,7 @@ async def test_acl_log(self, r_teardown, create_valkey):
         assert_resp_response_in(r, "client-info", expected, expected.keys())
         assert await r.acl_log_reset()
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_setuser_categories_without_prefix_fails(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -278,6 +289,7 @@ async def test_acl_setuser_categories_without_prefix_fails(self, r_teardown):
         with pytest.raises(exceptions.DataError):
             await r.acl_setuser(username, categories=["list"])
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_setuser_commands_without_prefix_fails(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -285,6 +297,7 @@ async def test_acl_setuser_commands_without_prefix_fails(self, r_teardown):
         with pytest.raises(exceptions.DataError):
             await r.acl_setuser(username, commands=["get"])
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_setuser_add_passwords_and_nopass_fails(self, r_teardown):
         username = "valkey-py-user"
         r = r_teardown(username)
@@ -292,11 +305,13 @@ async def test_acl_setuser_add_passwords_and_nopass_fails(self, r_teardown):
         with pytest.raises(exceptions.DataError):
             await r.acl_setuser(username, passwords="+mypass", nopass=True)
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_users(self, r: valkey.Valkey):
         users = await r.acl_users()
         assert isinstance(users, list)
         assert len(users) > 0
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_acl_whoami(self, r: valkey.Valkey):
         username = await r.acl_whoami()
         assert isinstance(username, (str, bytes))
@@ -307,6 +322,7 @@ async def test_client_list(self, r: valkey.Valkey):
         assert isinstance(clients[0], dict)
         assert "addr" in clients[0]
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_client_list_type(self, r: valkey.Valkey):
         with pytest.raises(exceptions.ValkeyError):
             await r.client_list(_type="not a client type")
@@ -314,10 +330,12 @@ async def test_client_list_type(self, r: valkey.Valkey):
             clients = await r.client_list(_type=client_type)
             assert isinstance(clients, list)
 
+    @skip_if_server_version_lt("5.0.0")
     @pytest.mark.onlynoncluster
     async def test_client_id(self, r: valkey.Valkey):
         assert await r.client_id() > 0
 
+    @skip_if_server_version_lt("5.0.0")
     @pytest.mark.onlynoncluster
     async def test_client_unblock(self, r: valkey.Valkey):
         myid = await r.client_id()
@@ -325,10 +343,12 @@ async def test_client_unblock(self, r: valkey.Valkey):
         assert not await r.client_unblock(myid, error=True)
         assert not await r.client_unblock(myid, error=False)
 
+    @skip_if_server_version_lt("2.6.9")
     @pytest.mark.onlynoncluster
     async def test_client_getname(self, r: valkey.Valkey):
         assert await r.client_getname() is None
 
+    @skip_if_server_version_lt("2.6.9")
     @pytest.mark.onlynoncluster
     async def test_client_setname(self, r: valkey.Valkey):
         assert await r.client_setname("valkey_py_test")
@@ -336,6 +356,7 @@ async def test_client_setname(self, r: valkey.Valkey):
             r, await r.client_getname(), "valkey_py_test", b"valkey_py_test"
         )
 
+    @skip_if_server_version_lt("7.2.0")
     async def test_client_setinfo(self, r: valkey.Valkey):
         await r.ping()
         info = await r.client_info()
@@ -357,6 +378,7 @@ async def test_client_setinfo(self, r: valkey.Valkey):
         assert info["lib-ver"] == ""
         await r3.aclose()
 
+    @skip_if_server_version_lt("2.6.9")
     @pytest.mark.onlynoncluster
     async def test_client_kill(self, r: valkey.Valkey, r2):
         await r.client_setname("valkey-py-c1")
@@ -381,6 +403,7 @@ async def test_client_kill(self, r: valkey.Valkey, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.8.12")
     async def test_client_kill_filter_invalid_params(self, r: valkey.Valkey):
         # empty
         with pytest.raises(exceptions.DataError):
@@ -394,6 +417,7 @@ async def test_client_kill_filter_invalid_params(self, r: valkey.Valkey):
         with pytest.raises(exceptions.DataError):
             await r.client_kill_filter(_type="caster")  # type: ignore
 
+    @skip_if_server_version_lt("2.8.12")
     @pytest.mark.onlynoncluster
     async def test_client_kill_filter_by_id(self, r: valkey.Valkey, r2):
         await r.client_setname("valkey-py-c1")
@@ -419,6 +443,7 @@ async def test_client_kill_filter_by_id(self, r: valkey.Valkey, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.8.12")
     @pytest.mark.onlynoncluster
     async def test_client_kill_filter_by_addr(self, r: valkey.Valkey, r2):
         await r.client_setname("valkey-py-c1")
@@ -444,12 +469,14 @@ async def test_client_kill_filter_by_addr(self, r: valkey.Valkey, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.6.9")
     async def test_client_list_after_client_setname(self, r: valkey.Valkey):
         await r.client_setname("valkey_py_test")
         clients = await r.client_list()
         # we don't know which client ours will be
         assert "valkey_py_test" in [c["name"] for c in clients]
 
+    @skip_if_server_version_lt("2.9.50")
     @pytest.mark.onlynoncluster
     async def test_client_pause(self, r: valkey.Valkey):
         assert await r.client_pause(1)
@@ -457,6 +484,7 @@ async def test_client_pause(self, r: valkey.Valkey):
         with pytest.raises(exceptions.ValkeyError):
             await r.client_pause(timeout="not an integer")
 
+    @skip_if_server_version_lt("7.2.0")
     @pytest.mark.onlynoncluster
     async def test_client_no_touch(self, r: valkey.Valkey):
         assert await r.client_no_touch("ON") == b"OK"
@@ -553,6 +581,7 @@ async def test_slowlog_length(self, r: valkey.Valkey, slowlog):
         await r.get("foo")
         assert isinstance(await r.slowlog_len(), int)
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_time(self, r: valkey.Valkey):
         t = await r.time()
         assert len(t) == 2
@@ -576,6 +605,7 @@ async def test_append(self, r: valkey.Valkey):
         assert await r.append("a", "a2") == 4
         assert await r.get("a") == b"a1a2"
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_bitcount(self, r: valkey.Valkey):
         await r.setbit("a", 5, True)
         assert await r.bitcount("a") == 1
@@ -594,12 +624,14 @@ async def test_bitcount(self, r: valkey.Valkey):
         assert await r.bitcount("a", -2, -1) == 2
         assert await r.bitcount("a", 1, 1) == 1
 
+    @skip_if_server_version_lt("2.6.0")
     @pytest.mark.onlynoncluster
     async def test_bitop_not_empty_string(self, r: valkey.Valkey):
         await r.set("a", "")
         await r.bitop("not", "r", "a")
         assert await r.get("r") is None
 
+    @skip_if_server_version_lt("2.6.0")
     @pytest.mark.onlynoncluster
     async def test_bitop_not(self, r: valkey.Valkey):
         test_str = b"\xAA\x00\xFF\x55"
@@ -608,6 +640,7 @@ async def test_bitop_not(self, r: valkey.Valkey):
         await r.bitop("not", "r", "a")
         assert int(binascii.hexlify(await r.get("r")), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     @pytest.mark.onlynoncluster
     async def test_bitop_not_in_place(self, r: valkey.Valkey):
         test_str = b"\xAA\x00\xFF\x55"
@@ -616,6 +649,7 @@ async def test_bitop_not_in_place(self, r: valkey.Valkey):
         await r.bitop("not", "a", "a")
         assert int(binascii.hexlify(await r.get("a")), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     @pytest.mark.onlynoncluster
     async def test_bitop_single_string(self, r: valkey.Valkey):
         test_str = b"\x01\x02\xFF"
@@ -627,6 +661,7 @@ async def test_bitop_single_string(self, r: valkey.Valkey):
         assert await r.get("res2") == test_str
         assert await r.get("res3") == test_str
 
+    @skip_if_server_version_lt("2.6.0")
     @pytest.mark.onlynoncluster
     async def test_bitop_string_operands(self, r: valkey.Valkey):
         await r.set("a", b"\x01\x02\xFF\xFF")
@@ -639,6 +674,7 @@ async def test_bitop_string_operands(self, r: valkey.Valkey):
         assert int(binascii.hexlify(await r.get("res3")), 16) == 0x000000FF
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.7")
     async def test_bitpos(self, r: valkey.Valkey):
         key = "key:bitpos"
         await r.set(key, b"\xff\xf0\x00")
@@ -651,6 +687,7 @@ async def test_bitpos(self, r: valkey.Valkey):
         await r.set(key, b"\x00\x00\x00")
         assert await r.bitpos(key, 1) == -1
 
+    @skip_if_server_version_lt("2.8.7")
     async def test_bitpos_wrong_arguments(self, r: valkey.Valkey):
         key = "key:bitpos:wrong:args"
         await r.set(key, b"\xff\xf0\x00")
@@ -689,12 +726,14 @@ async def test_delitem(self, r: valkey.Valkey):
         await r.delete("a")
         assert await r.get("a") is None
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_unlink(self, r: valkey.Valkey):
         assert await r.unlink("a") == 0
         await r.set("a", "foo")
         assert await r.unlink("a") == 1
         assert await r.get("a") is None
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_unlink_with_multiple_keys(self, r: valkey.Valkey):
         await r.set("a", "foo")
         await r.set("b", "bar")
@@ -702,6 +741,7 @@ async def test_unlink_with_multiple_keys(self, r: valkey.Valkey):
         assert await r.get("a") is None
         assert await r.get("b") is None
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_dump_and_restore(self, r: valkey.Valkey):
         await r.set("a", "foo")
         dumped = await r.dump("a")
@@ -709,6 +749,7 @@ async def test_dump_and_restore(self, r: valkey.Valkey):
         await r.restore("a", 0, dumped)
         assert await r.get("a") == b"foo"
 
+    @skip_if_server_version_lt("3.0.0")
     async def test_dump_and_restore_and_replace(self, r: valkey.Valkey):
         await r.set("a", "bar")
         dumped = await r.dump("a")
@@ -718,6 +759,7 @@ async def test_dump_and_restore_and_replace(self, r: valkey.Valkey):
         await r.restore("a", 0, dumped, replace=True)
         assert await r.get("a") == b"bar"
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_dump_and_restore_absttl(self, r: valkey.Valkey):
         await r.set("a", "foo")
         dumped = await r.dump("a")
@@ -820,6 +862,7 @@ async def test_incrby(self, r: valkey.Valkey):
         assert await r.incrby("a", 4) == 5
         assert await r.get("a") == b"5"
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_incrbyfloat(self, r: valkey.Valkey):
         assert await r.incrbyfloat("a") == 1.0
         assert await r.get("a") == b"1"
@@ -862,6 +905,7 @@ async def test_msetnx(self, r: valkey.Valkey):
             assert await r.get(k) == v
         assert await r.get("d") is None
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_pexpire(self, r: valkey.Valkey):
         assert not await r.pexpire("a", 60000)
         await r.set("a", "foo")
@@ -870,16 +914,19 @@ async def test_pexpire(self, r: valkey.Valkey):
         assert await r.persist("a")
         assert await r.pttl("a") == -1
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_pexpireat_datetime(self, r: valkey.Valkey):
         expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1)
         await r.set("a", "foo")
         assert await r.pexpireat("a", expire_at)
         assert 0 < await r.pttl("a") <= 61000
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_pexpireat_no_key(self, r: valkey.Valkey):
         expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1)
         assert not await r.pexpireat("a", expire_at)
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_pexpireat_unixtime(self, r: valkey.Valkey):
         expire_at = await valkey_server_time(r) + datetime.timedelta(minutes=1)
         await r.set("a", "foo")
@@ -887,17 +934,20 @@ async def test_pexpireat_unixtime(self, r: valkey.Valkey):
         assert await r.pexpireat("a", expire_at_milliseconds)
         assert 0 < await r.pttl("a") <= 61000
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_psetex(self, r: valkey.Valkey):
         assert await r.psetex("a", 1000, "value")
         assert await r.get("a") == b"value"
         assert 0 < await r.pttl("a") <= 1000
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_psetex_timedelta(self, r: valkey.Valkey):
         expire_at = datetime.timedelta(milliseconds=1000)
         assert await r.psetex("a", expire_at, "value")
         assert await r.get("a") == b"value"
         assert 0 < await r.pttl("a") <= 1000
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_pttl(self, r: valkey.Valkey):
         assert not await r.pexpire("a", 10000)
         await r.set("a", "1")
@@ -906,10 +956,12 @@ async def test_pttl(self, r: valkey.Valkey):
         assert await r.persist("a")
         assert await r.pttl("a") == -1
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_pttl_no_key(self, r: valkey.Valkey):
         """PTTL on servers 2.8 and after return -2 when the key doesn't exist"""
         assert await r.pttl("a") == -2
 
+    @skip_if_server_version_lt("6.2.0")
     async def test_hrandfield(self, r):
         assert await r.hrandfield("key") is None
         await r.hset("key", mapping={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5})
@@ -944,11 +996,13 @@ async def test_renamenx(self, r: valkey.Valkey):
         assert await r.get("a") == b"1"
         assert await r.get("b") == b"2"
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_nx(self, r: valkey.Valkey):
         assert await r.set("a", "1", nx=True)
         assert not await r.set("a", "2", nx=True)
         assert await r.get("a") == b"1"
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_xx(self, r: valkey.Valkey):
         assert not await r.set("a", "1", xx=True)
         assert await r.get("a") is None
@@ -956,32 +1010,38 @@ async def test_set_xx(self, r: valkey.Valkey):
         assert await r.set("a", "2", xx=True)
         assert await r.get("a") == b"2"
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_px(self, r: valkey.Valkey):
         assert await r.set("a", "1", px=10000)
         assert await r.get("a") == b"1"
         assert 0 < await r.pttl("a") <= 10000
         assert 0 < await r.ttl("a") <= 10
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_px_timedelta(self, r: valkey.Valkey):
         expire_at = datetime.timedelta(milliseconds=1000)
         assert await r.set("a", "1", px=expire_at)
         assert 0 < await r.pttl("a") <= 1000
         assert 0 < await r.ttl("a") <= 1
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_ex(self, r: valkey.Valkey):
         assert await r.set("a", "1", ex=10)
         assert 0 < await r.ttl("a") <= 10
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_ex_timedelta(self, r: valkey.Valkey):
         expire_at = datetime.timedelta(seconds=60)
         assert await r.set("a", "1", ex=expire_at)
         assert 0 < await r.ttl("a") <= 60
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_set_multipleoptions(self, r: valkey.Valkey):
         await r.set("a", "val")
         assert await r.set("a", "1", xx=True, px=10000)
         assert 0 < await r.ttl("a") <= 10
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     async def test_set_keepttl(self, r: valkey.Valkey):
         await r.set("a", "val")
         assert await r.set("a", "1", xx=True, px=10000)
@@ -1026,6 +1086,7 @@ async def test_ttl(self, r: valkey.Valkey):
         assert await r.persist("a")
         assert await r.ttl("a") == -1
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_ttl_nokey(self, r: valkey.Valkey):
         """TTL on servers 2.8 and after return -2 when the key doesn't exist"""
         assert await r.ttl("a") == -2
@@ -1191,6 +1252,7 @@ async def test_rpush(self, r: valkey.Valkey):
         assert await r.rpush("a", "3", "4") == 4
         assert await r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"]
 
+    @skip_if_server_version_lt("6.0.6")
     async def test_lpos(self, r: valkey.Valkey):
         assert await r.rpush("a", "a", "b", "c", "1", "2", "3", "c", "c") == 8
         assert await r.lpos("a", "a") == 0
@@ -1230,6 +1292,7 @@ async def test_rpushx(self, r: valkey.Valkey):
         assert await r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"]
 
     # SCAN COMMANDS
+    @skip_if_server_version_lt("2.8.0")
     @pytest.mark.onlynoncluster
     async def test_scan(self, r: valkey.Valkey):
         await r.set("a", 1)
@@ -1241,6 +1304,7 @@ async def test_scan(self, r: valkey.Valkey):
         _, keys = await r.scan(match="a")
         assert set(keys) == {b"a"}
 
+    @skip_if_server_version_lt(VALKEY_6_VERSION)
     @pytest.mark.onlynoncluster
     async def test_scan_type(self, r: valkey.Valkey):
         await r.sadd("a-set", 1)
@@ -1249,6 +1313,7 @@ async def test_scan_type(self, r: valkey.Valkey):
         _, keys = await r.scan(match="a*", _type="SET")
         assert set(keys) == {b"a-set"}
 
+    @skip_if_server_version_lt("2.8.0")
     @pytest.mark.onlynoncluster
     async def test_scan_iter(self, r: valkey.Valkey):
         await r.set("a", 1)
@@ -1259,6 +1324,7 @@ async def test_scan_iter(self, r: valkey.Valkey):
         keys = [k async for k in r.scan_iter(match="a")]
         assert set(keys) == {b"a"}
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_sscan(self, r: valkey.Valkey):
         await r.sadd("a", 1, 2, 3)
         cursor, members = await r.sscan("a")
@@ -1267,6 +1333,7 @@ async def test_sscan(self, r: valkey.Valkey):
         _, members = await r.sscan("a", match=b"1")
         assert set(members) == {b"1"}
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_sscan_iter(self, r: valkey.Valkey):
         await r.sadd("a", 1, 2, 3)
         members = [k async for k in r.sscan_iter("a")]
@@ -1274,6 +1341,7 @@ async def test_sscan_iter(self, r: valkey.Valkey):
         members = [k async for k in r.sscan_iter("a", match=b"1")]
         assert set(members) == {b"1"}
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_hscan(self, r: valkey.Valkey):
         await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
         cursor, dic = await r.hscan("a")
@@ -1295,6 +1363,7 @@ async def test_hscan_novalues(self, r: valkey.Valkey):
         _, keys = await r.hscan("a_notset", match="a", no_values=True)
         assert keys == []
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_hscan_iter(self, r: valkey.Valkey):
         await r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
         dic = {k: v async for k, v in r.hscan_iter("a")}
@@ -1316,6 +1385,7 @@ async def test_hscan_iter_novalues(self, r: valkey.Valkey):
         )
         assert keys == []
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_zscan(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 1, "b": 2, "c": 3})
         cursor, pairs = await r.zscan("a")
@@ -1324,6 +1394,7 @@ async def test_zscan(self, r: valkey.Valkey):
         _, pairs = await r.zscan("a", match="a")
         assert set(pairs) == {(b"a", 1)}
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_zscan_iter(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 1, "b": 2, "c": 3})
         pairs = [k async for k in r.zscan_iter("a")]
@@ -1399,6 +1470,7 @@ async def test_spop(self, r: valkey.Valkey):
         assert value in s
         assert await r.smembers("a") == set(s) - {value}
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_spop_multi_value(self, r: valkey.Valkey):
         s = [b"1", b"2", b"3"]
         await r.sadd("a", *s)
@@ -1418,6 +1490,7 @@ async def test_srandmember(self, r: valkey.Valkey):
         await r.sadd("a", *s)
         assert await r.srandmember("a") in s
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_srandmember_multi_value(self, r: valkey.Valkey):
         s = [b"1", b"2", b"3"]
         await r.sadd("a", *s)
@@ -1513,6 +1586,7 @@ async def test_zcount(self, r: valkey.Valkey):
         assert await r.zcount("a", 10, 20) == 0
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     async def test_zdiff(self, r):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         await r.zadd("b", {"a1": 1, "a2": 2})
@@ -1521,6 +1595,7 @@ async def test_zdiff(self, r):
         assert_resp_response(r, response, [b"a3", b"3"], [[b"a3", 3.0]])
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     async def test_zdiffstore(self, r):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         await r.zadd("b", {"a1": 1, "a2": 2})
@@ -1536,6 +1611,7 @@ async def test_zincrby(self, r: valkey.Valkey):
         assert await r.zscore("a", "a2") == 3.0
         assert await r.zscore("a", "a3") == 8.0
 
+    @skip_if_server_version_lt("2.8.9")
     async def test_zlexcount(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert await r.zlexcount("a", "-", "+") == 7
@@ -1585,6 +1661,7 @@ async def test_zinterstore_with_weight(self, r: valkey.Valkey):
             r, response, [(b"a3", 20), (b"a1", 23)], [[b"a3", 20], [b"a1", 23]]
         )
 
+    @skip_if_server_version_lt("4.9.0")
     async def test_zpopmax(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         response = await r.zpopmax("a")
@@ -1596,6 +1673,7 @@ async def test_zpopmax(self, r: valkey.Valkey):
             r, response, [(b"a2", 2), (b"a1", 1)], [[b"a2", 2], [b"a1", 1]]
         )
 
+    @skip_if_server_version_lt("4.9.0")
     async def test_zpopmin(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         response = await r.zpopmin("a")
@@ -1607,6 +1685,7 @@ async def test_zpopmin(self, r: valkey.Valkey):
             r, response, [(b"a2", 2), (b"a3", 3)], [[b"a2", 2], [b"a3", 3]]
         )
 
+    @skip_if_server_version_lt("4.9.0")
     @pytest.mark.onlynoncluster
     async def test_bzpopmax(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2})
@@ -1641,6 +1720,7 @@ async def test_bzpopmax(self, r: valkey.Valkey):
             r, await r.bzpopmax("c", timeout=1), (b"c", b"c1", 100), [b"c", b"c1", 100]
         )
 
+    @skip_if_server_version_lt("4.9.0")
     @pytest.mark.onlynoncluster
     async def test_bzpopmin(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2})
@@ -1696,6 +1776,7 @@ async def test_zrange(self, r: valkey.Valkey):
         #     (b"a2", 2),
         # ]
 
+    @skip_if_server_version_lt("2.8.9")
     async def test_zrangebylex(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert await r.zrangebylex("a", "-", "[c") == [b"a", b"b", b"c"]
@@ -1704,6 +1785,7 @@ async def test_zrangebylex(self, r: valkey.Valkey):
         assert await r.zrangebylex("a", "[f", "+") == [b"f", b"g"]
         assert await r.zrangebylex("a", "-", "+", start=3, num=2) == [b"d", b"e"]
 
+    @skip_if_server_version_lt("2.9.9")
     async def test_zrevrangebylex(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert await r.zrevrangebylex("a", "[c", "-") == [b"c", b"b", b"a"]
@@ -1751,6 +1833,7 @@ async def test_zrank(self, r: valkey.Valkey):
         assert await r.zrank("a", "a2") == 1
         assert await r.zrank("a", "a6") is None
 
+    @skip_if_server_version_lt("7.2.0")
     async def test_zrank_withscore(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
         assert await r.zrank("a", "a1") == 0
@@ -1773,6 +1856,7 @@ async def test_zrem_multiple_keys(self, r: valkey.Valkey):
         assert await r.zrem("a", "a1", "a2") == 2
         assert await r.zrange("a", 0, 5) == [b"a3"]
 
+    @skip_if_server_version_lt("2.8.9")
     async def test_zremrangebylex(self, r: valkey.Valkey):
         await r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert await r.zremrangebylex("a", "-", "[c") == 3
@@ -1848,6 +1932,7 @@ async def test_zrevrank(self, r: valkey.Valkey):
         assert await r.zrevrank("a", "a2") == 3
         assert await r.zrevrank("a", "a6") is None
 
+    @skip_if_server_version_lt("7.2.0")
     async def test_zrevrank_withscore(self, r: valkey.Valkey):
         await r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
         assert await r.zrevrank("a", "a1") == 4
@@ -1921,12 +2006,14 @@ async def test_zunionstore_with_weight(self, r: valkey.Valkey):
         )
 
     # HYPERLOGLOG TESTS
+    @skip_if_server_version_lt("2.8.9")
     async def test_pfadd(self, r: valkey.Valkey):
         members = {b"1", b"2", b"3"}
         assert await r.pfadd("a", *members) == 1
         assert await r.pfadd("a", *members) == 0
         assert await r.pfcount("a") == len(members)
 
+    @skip_if_server_version_lt("2.8.9")
     @pytest.mark.onlynoncluster
     async def test_pfcount(self, r: valkey.Valkey):
         members = {b"1", b"2", b"3"}
@@ -1937,6 +2024,7 @@ async def test_pfcount(self, r: valkey.Valkey):
         assert await r.pfcount("b") == len(members_b)
         assert await r.pfcount("a", "b") == len(members_b.union(members))
 
+    @skip_if_server_version_lt("2.8.9")
     @pytest.mark.onlynoncluster
     async def test_pfmerge(self, r: valkey.Valkey):
         mema = {b"1", b"2", b"3"}
@@ -2009,6 +2097,7 @@ async def test_hincrby(self, r: valkey.Valkey):
         assert await r.hincrby("a", "1", amount=2) == 3
         assert await r.hincrby("a", "1", amount=-2) == 1
 
+    @skip_if_server_version_lt("2.6.0")
     async def test_hincrbyfloat(self, r: valkey.Valkey):
         assert await r.hincrbyfloat("a", "1") == 1.0
         assert await r.hincrbyfloat("a", "1") == 2.0
@@ -2053,6 +2142,7 @@ async def test_hvals(self, r: valkey.Valkey):
         remote_vals = await r.hvals("a")
         assert sorted(local_vals) == sorted(remote_vals)
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_hstrlen(self, r: valkey.Valkey):
         await r.hset("a", mapping={"1": "22", "2": "333"})
         assert await r.hstrlen("a", "1") == 2
@@ -2278,16 +2368,25 @@ async def test_cluster_slaves(self, mock_cluster_resp_slaves):
             await mock_cluster_resp_slaves.cluster("slaves", "nodeid"), dict
         )
 
+    @skip_if_server_version_lt("3.0.0")
+    @skip_if_server_version_gte("7.0.0")
+    @pytest.mark.onlynoncluster
+    async def test_readwrite(self, r: valkey.Valkey):
+        assert await r.readwrite()
+
+    @skip_if_server_version_lt("3.0.0")
     @pytest.mark.onlynoncluster
     async def test_readonly_invalid_cluster_state(self, r: valkey.Valkey):
         with pytest.raises(exceptions.ValkeyError):
             await r.readonly()
 
+    @skip_if_server_version_lt("3.0.0")
     @pytest.mark.onlynoncluster
     async def test_readonly(self, mock_cluster_resp_ok):
         assert await mock_cluster_resp_ok.readonly() is True
 
     # GEO COMMANDS
+    @skip_if_server_version_lt("3.2.0")
     async def test_geoadd(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2298,10 +2397,12 @@ async def test_geoadd(self, r: valkey.Valkey):
         assert await r.geoadd("barcelona", values) == 2
         assert await r.zcard("barcelona") == 2
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geoadd_invalid_params(self, r: valkey.Valkey):
         with pytest.raises(exceptions.ValkeyError):
             await r.geoadd("barcelona", (1, 2))
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geodist(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2312,6 +2413,7 @@ async def test_geodist(self, r: valkey.Valkey):
         assert await r.geoadd("barcelona", values) == 2
         assert await r.geodist("barcelona", "place1", "place2") == 3067.4157
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geodist_units(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2322,15 +2424,18 @@ async def test_geodist_units(self, r: valkey.Valkey):
         await r.geoadd("barcelona", values)
         assert await r.geodist("barcelona", "place1", "place2", "km") == 3.0674
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geodist_missing_one_member(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1")
         await r.geoadd("barcelona", values)
         assert await r.geodist("barcelona", "place1", "missing_member", "km") is None
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geodist_invalid_units(self, r: valkey.Valkey):
         with pytest.raises(exceptions.ValkeyError):
             assert await r.geodist("x", "y", "z", "inches")
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geohash(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2346,6 +2451,7 @@ async def test_geohash(self, r: valkey.Valkey):
             [b"sp3e9yg3kd0", b"sp3e9cbc3t0", None],
         )
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_geopos(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2368,9 +2474,16 @@ async def test_geopos(self, r: valkey.Valkey):
             ],
         )
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_geopos_no_value(self, r: valkey.Valkey):
         assert await r.geopos("barcelona", "place1", "place2") == [None, None]
 
+    @skip_if_server_version_lt("3.2.0")
+    @skip_if_server_version_gte("4.0.0")
+    async def test_old_geopos_no_value(self, r: valkey.Valkey):
+        assert await r.geopos("barcelona", "place1", "place2") == []
+
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2382,6 +2495,7 @@ async def test_georadius(self, r: valkey.Valkey):
         assert await r.georadius("barcelona", 2.191, 41.433, 1000) == [b"place1"]
         assert await r.georadius("barcelona", 2.187, 41.406, 1000) == [b"\x80place2"]
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius_no_values(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2392,6 +2506,7 @@ async def test_georadius_no_values(self, r: valkey.Valkey):
         await r.geoadd("barcelona", values)
         assert await r.georadius("barcelona", 1, 2, 1000) == []
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius_units(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2405,6 +2520,7 @@ async def test_georadius_units(self, r: valkey.Valkey):
         ]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius_with(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2459,6 +2575,7 @@ async def test_georadius_with(self, r: valkey.Valkey):
             == []
         )
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius_count(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2471,6 +2588,7 @@ async def test_georadius_count(self, r: valkey.Valkey):
             b"place1"
         ]
 
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadius_sort(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2488,6 +2606,7 @@ async def test_georadius_sort(self, r: valkey.Valkey):
             b"place1",
         ]
 
+    @skip_if_server_version_lt("3.2.0")
     @pytest.mark.onlynoncluster
     async def test_georadius_store(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
@@ -2501,6 +2620,7 @@ async def test_georadius_store(self, r: valkey.Valkey):
         assert await r.zrange("places_barcelona", 0, -1) == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     @pytest.mark.onlynoncluster
     async def test_georadius_store_dist(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
@@ -2517,6 +2637,7 @@ async def test_georadius_store_dist(self, r: valkey.Valkey):
         assert await r.zscore("places_barcelona", "place1") == 88.05060698409301
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     async def test_georadiusmember(self, r: valkey.Valkey):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2548,6 +2669,7 @@ async def test_georadiusmember(self, r: valkey.Valkey):
             ],
         ]
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xack(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2568,6 +2690,7 @@ async def test_xack(self, r: valkey.Valkey):
         assert await r.xack(stream, group, m1) == 1
         assert await r.xack(stream, group, m2, m3) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xadd(self, r: valkey.Valkey):
         stream = "stream"
         message_id = await r.xadd(stream, {"foo": "bar"})
@@ -2581,6 +2704,7 @@ async def test_xadd(self, r: valkey.Valkey):
         await r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False)
         assert await r.xlen(stream) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xclaim(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2618,6 +2742,7 @@ async def test_xclaim(self, r: valkey.Valkey):
             justid=True,
         ) == [message_id]
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_xclaim_trimmed(self, r: valkey.Valkey):
         # xclaim should not raise an exception if the item is not there
         stream = "stream"
@@ -2641,6 +2766,7 @@ async def test_xclaim_trimmed(self, r: valkey.Valkey):
         assert len(item) == 1
         assert item[0][0] == sid2
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xdel(self, r: valkey.Valkey):
         stream = "stream"
 
@@ -2655,6 +2781,7 @@ async def test_xdel(self, r: valkey.Valkey):
         assert await r.xdel(stream, m1) == 1
         assert await r.xdel(stream, m2, m3) == 2
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_xgroup_create(self, r: valkey.Valkey):
         # tests xgroup_create and xinfo_groups
         stream = "stream"
@@ -2677,6 +2804,7 @@ async def test_xgroup_create(self, r: valkey.Valkey):
         ]
         assert await r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_xgroup_create_mkstream(self, r: valkey.Valkey):
         # tests xgroup_create and xinfo_groups
         stream = "stream"
@@ -2702,6 +2830,7 @@ async def test_xgroup_create_mkstream(self, r: valkey.Valkey):
         ]
         assert await r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xgroup_delconsumer(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2719,6 +2848,7 @@ async def test_xgroup_delconsumer(self, r: valkey.Valkey):
         # deleting the consumer should return 2 pending messages
         assert await r.xgroup_delconsumer(stream, group, consumer) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xgroup_destroy(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2730,6 +2860,7 @@ async def test_xgroup_destroy(self, r: valkey.Valkey):
         await r.xgroup_create(stream, group, 0)
         assert await r.xgroup_destroy(stream, group)
 
+    @skip_if_server_version_lt("7.0.0")
     async def test_xgroup_setid(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2750,6 +2881,7 @@ async def test_xgroup_setid(self, r: valkey.Valkey):
         ]
         assert await r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("7.2.0")
     async def test_xinfo_consumers(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2776,6 +2908,7 @@ async def test_xinfo_consumers(self, r: valkey.Valkey):
         assert isinstance(info[1].pop("inactive"), int)
         assert info == expected
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xinfo_stream(self, r: valkey.Valkey):
         stream = "stream"
         m1 = await r.xadd(stream, {"foo": "bar"})
@@ -2786,6 +2919,7 @@ async def test_xinfo_stream(self, r: valkey.Valkey):
         assert info["first-entry"] == await get_stream_message(r, stream, m1)
         assert info["last-entry"] == await get_stream_message(r, stream, m2)
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xlen(self, r: valkey.Valkey):
         stream = "stream"
         assert await r.xlen(stream) == 0
@@ -2793,6 +2927,7 @@ async def test_xlen(self, r: valkey.Valkey):
         await r.xadd(stream, {"foo": "bar"})
         assert await r.xlen(stream) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xpending(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2821,6 +2956,7 @@ async def test_xpending(self, r: valkey.Valkey):
         }
         assert await r.xpending(stream, group) == expected
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xpending_range(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2844,6 +2980,7 @@ async def test_xpending_range(self, r: valkey.Valkey):
         assert response[1]["message_id"] == m2
         assert response[1]["consumer"] == consumer2.encode()
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xrange(self, r: valkey.Valkey):
         stream = "stream"
         m1 = await r.xadd(stream, {"foo": "bar"})
@@ -2866,6 +3003,7 @@ def get_ids(results):
         results = await r.xrange(stream, max=m2, count=1)
         assert get_ids(results) == [m1]
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xread(self, r: valkey.Valkey):
         stream = "stream"
         m1 = await r.xadd(stream, {"foo": "bar"})
@@ -2896,6 +3034,7 @@ async def test_xread(self, r: valkey.Valkey):
             r, res, [[strem_name, expected_entries]], {strem_name: [expected_entries]}
         )
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xreadgroup(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -2962,6 +3101,7 @@ async def test_xreadgroup(self, r: valkey.Valkey):
             r, res, [[strem_name, expected_entries]], {strem_name: [expected_entries]}
         )
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xrevrange(self, r: valkey.Valkey):
         stream = "stream"
         m1 = await r.xadd(stream, {"foo": "bar"})
@@ -2984,6 +3124,7 @@ def get_ids(results):
         results = await r.xrevrange(stream, min=m2, count=1)
         assert get_ids(results) == [m4]
 
+    @skip_if_server_version_lt("5.0.0")
     async def test_xtrim(self, r: valkey.Valkey):
         stream = "stream"
 
@@ -3072,6 +3213,7 @@ async def test_bitfield_operations(self, r: valkey.Valkey):
         )
         assert resp == [0, None, 255]
 
+    @skip_if_server_version_lt("6.0.0")
     async def test_bitfield_ro(self, r: valkey.Valkey):
         bf = r.bitfield("a")
         resp = await bf.set("u8", 8, 255).execute()
@@ -3084,6 +3226,7 @@ async def test_bitfield_ro(self, r: valkey.Valkey):
         resp = await r.bitfield_ro("a", "u8", 0, items)
         assert resp == [0, 15, 15, 14]
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_stats(self, r: valkey.Valkey):
         # put a key into the current db to make sure that "db.<current-db>"
         # has data
@@ -3094,10 +3237,12 @@ async def test_memory_stats(self, r: valkey.Valkey):
             if key.startswith("db."):
                 assert isinstance(value, dict)
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_memory_usage(self, r: valkey.Valkey):
         await r.set("foo", "bar")
         assert isinstance(await r.memory_usage("foo"), int)
 
+    @skip_if_server_version_lt("4.0.0")
     async def test_module_list(self, r: valkey.Valkey):
         assert isinstance(await r.module_list(), list)
         for x in await r.module_list():
diff --git a/tests/test_asyncio/test_connection.py b/tests/test_asyncio/test_connection.py
index 2d15e615..4da68b43 100644
--- a/tests/test_asyncio/test_connection.py
+++ b/tests/test_asyncio/test_connection.py
@@ -5,6 +5,7 @@
 
 import pytest
 import valkey
+from tests.conftest import skip_if_server_version_lt
 from valkey._parsers import (
     _AsyncHiredisParser,
     _AsyncRESP2Parser,
@@ -89,6 +90,7 @@ async def get_conn(_):
     await r.aclose()
 
 
+@skip_if_server_version_lt("4.0.0")
 @pytest.mark.valkeymod
 @pytest.mark.onlynoncluster
 async def test_loading_external_modules(r):
diff --git a/tests/test_asyncio/test_connection_pool.py b/tests/test_asyncio/test_connection_pool.py
index e63f3f51..ce8d792a 100644
--- a/tests/test_asyncio/test_connection_pool.py
+++ b/tests/test_asyncio/test_connection_pool.py
@@ -4,6 +4,7 @@
 import pytest
 import pytest_asyncio
 import valkey.asyncio as valkey
+from tests.conftest import skip_if_server_version_lt
 from valkey.asyncio.connection import Connection, to_bool
 
 from .compat import aclosing, mock
@@ -317,11 +318,13 @@ def test_port(self):
         assert pool.connection_class == valkey.Connection
         assert pool.connection_kwargs == {"host": "localhost", "port": 6380}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username(self):
         pool = valkey.ConnectionPool.from_url("valkey://myuser:@localhost")
         assert pool.connection_class == valkey.Connection
         assert pool.connection_kwargs == {"host": "localhost", "username": "myuser"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_quoted_username(self):
         pool = valkey.ConnectionPool.from_url(
             "valkey://%2Fmyuser%2F%2B name%3D%24+:@localhost"
@@ -347,6 +350,7 @@ def test_quoted_password(self):
             "password": "/mypass/+ word=$+",
         }
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username_and_password(self):
         pool = valkey.ConnectionPool.from_url("valkey://myuser:mypass@localhost")
         assert pool.connection_class == valkey.Connection
@@ -473,11 +477,13 @@ def test_defaults(self):
         assert pool.connection_class == valkey.UnixDomainSocketConnection
         assert pool.connection_kwargs == {"path": "/socket"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username(self):
         pool = valkey.ConnectionPool.from_url("unix://myuser:@/socket")
         assert pool.connection_class == valkey.UnixDomainSocketConnection
         assert pool.connection_kwargs == {"path": "/socket", "username": "myuser"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_quoted_username(self):
         pool = valkey.ConnectionPool.from_url(
             "unix://%2Fmyuser%2F%2B name%3D%24+:@/socket"
@@ -581,6 +587,7 @@ async def test_on_connect_error(self):
         assert not pool._available_connections[0]._reader
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     async def test_busy_loading_disconnects_socket(self, r):
         """
         If Valkey raises a LOADING error, the connection should be
@@ -592,6 +599,7 @@ async def test_busy_loading_disconnects_socket(self, r):
             assert not r.connection._reader
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     async def test_busy_loading_from_pipeline_immediate_command(self, r):
         """
         BusyLoadingErrors should raise from Pipelines that execute a
@@ -608,6 +616,7 @@ async def test_busy_loading_from_pipeline_immediate_command(self, r):
         assert not pool._available_connections[0]._reader
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     async def test_busy_loading_from_pipeline(self, r):
         """
         BusyLoadingErrors should be raised from a pipeline execution
@@ -622,6 +631,7 @@ async def test_busy_loading_from_pipeline(self, r):
         assert len(pool._available_connections) == 1
         assert not pool._available_connections[0]._reader
 
+    @skip_if_server_version_lt("2.8.8")
     async def test_read_only_error(self, r):
         """READONLY errors get turned into ReadOnlyError exceptions"""
         with pytest.raises(valkey.ReadOnlyError):
diff --git a/tests/test_asyncio/test_pipeline.py b/tests/test_asyncio/test_pipeline.py
index d2161d91..5021f91c 100644
--- a/tests/test_asyncio/test_pipeline.py
+++ b/tests/test_asyncio/test_pipeline.py
@@ -1,5 +1,6 @@
 import pytest
 import valkey
+from tests.conftest import skip_if_server_version_lt
 
 from .compat import aclosing, mock
 from .conftest import wait_for_command
@@ -392,6 +393,7 @@ async def test_pipeline_get(self, r):
             assert await pipe.execute() == [b"a1"]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.0.0")
     async def test_pipeline_discard(self, r):
         # empty pipeline should raise an error
         async with r.pipeline() as pipe:
diff --git a/tests/test_asyncio/test_pubsub.py b/tests/test_asyncio/test_pubsub.py
index afb6530d..d27e5941 100644
--- a/tests/test_asyncio/test_pubsub.py
+++ b/tests/test_asyncio/test_pubsub.py
@@ -15,7 +15,7 @@
 import pytest
 import pytest_asyncio
 import valkey.asyncio as valkey
-from tests.conftest import get_protocol_version
+from tests.conftest import get_protocol_version, skip_if_server_version_lt
 from valkey.exceptions import ConnectionError
 from valkey.typing import EncodableT
 from valkey.utils import HIREDIS_AVAILABLE
@@ -608,6 +608,7 @@ async def test_channel_subscribe(self, r: valkey.Valkey):
 @pytest.mark.onlynoncluster
 class TestPubSubSubcommands:
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     async def test_pubsub_channels(self, r: valkey.Valkey, pubsub):
         p = pubsub
         await p.subscribe("foo", "bar", "baz", "quux")
@@ -617,6 +618,7 @@ async def test_pubsub_channels(self, r: valkey.Valkey, pubsub):
         assert all([channel in await r.pubsub_channels() for channel in expected])
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     async def test_pubsub_numsub(self, r: valkey.Valkey):
         p1 = r.pubsub()
         await p1.subscribe("foo", "bar", "baz")
@@ -636,6 +638,7 @@ async def test_pubsub_numsub(self, r: valkey.Valkey):
         await p2.aclose()
         await p3.aclose()
 
+    @skip_if_server_version_lt("2.8.0")
     async def test_pubsub_numpat(self, r: valkey.Valkey):
         p = r.pubsub()
         await p.psubscribe("*oo", "*ar", "b*z")
@@ -647,6 +650,7 @@ async def test_pubsub_numpat(self, r: valkey.Valkey):
 
 @pytest.mark.onlynoncluster
 class TestPubSubPings:
+    @skip_if_server_version_lt("3.0.0")
     async def test_send_pubsub_ping(self, r: valkey.Valkey):
         p = r.pubsub(ignore_subscribe_messages=True)
         await p.subscribe("foo")
@@ -656,6 +660,7 @@ async def test_send_pubsub_ping(self, r: valkey.Valkey):
         )
         await p.aclose()
 
+    @skip_if_server_version_lt("3.0.0")
     async def test_send_pubsub_ping_message(self, r: valkey.Valkey):
         p = r.pubsub(ignore_subscribe_messages=True)
         await p.subscribe("foo")
@@ -668,6 +673,7 @@ async def test_send_pubsub_ping_message(self, r: valkey.Valkey):
 
 @pytest.mark.onlynoncluster
 class TestPubSubConnectionKilled:
+    @skip_if_server_version_lt("3.0.0")
     async def test_connection_error_raised_when_connection_dies(
         self, r: valkey.Valkey, pubsub
     ):
diff --git a/tests/test_asyncio/test_scripting.py b/tests/test_asyncio/test_scripting.py
index b756b928..42269ad0 100644
--- a/tests/test_asyncio/test_scripting.py
+++ b/tests/test_asyncio/test_scripting.py
@@ -1,5 +1,6 @@
 import pytest
 import pytest_asyncio
+from tests.conftest import skip_if_server_version_lt
 from valkey import exceptions
 
 multiply_script = """
@@ -35,6 +36,7 @@ async def test_eval(self, r):
         assert await r.eval(multiply_script, 1, "a", 3) == 6
 
     @pytest.mark.asyncio(forbid_global_loop=True)
+    @skip_if_server_version_lt("6.2.0")
     async def test_script_flush(self, r):
         await r.set("a", 2)
         await r.script_load(multiply_script)
diff --git a/tests/test_cluster.py b/tests/test_cluster.py
index e9fbaec7..b78418f2 100644
--- a/tests/test_cluster.py
+++ b/tests/test_cluster.py
@@ -45,6 +45,7 @@
     _get_client,
     assert_resp_response,
     is_resp2_connection,
+    skip_if_server_version_lt,
     skip_unless_arch_bits,
     wait_for_command,
 )
@@ -1105,6 +1106,7 @@ def test_pubsub_numpat_merge_results(self, r):
         # subscribed to this channel in the entire cluster
         assert r.pubsub_numpat(target_nodes="all") == len(nodes)
 
+    @skip_if_server_version_lt("2.8.0")
     def test_cluster_pubsub_channels(self, r):
         p = r.pubsub()
         p.subscribe("foo", "bar", "baz", "quux")
@@ -1115,6 +1117,7 @@ def test_cluster_pubsub_channels(self, r):
             [channel in r.pubsub_channels(target_nodes="all") for channel in expected]
         )
 
+    @skip_if_server_version_lt("2.8.0")
     def test_cluster_pubsub_numsub(self, r):
         p1 = r.pubsub()
         p1.subscribe("foo", "bar", "baz")
@@ -1144,6 +1147,7 @@ def test_cluster_slots(self, r):
         assert cluster_slots.get((0, 8191)) is not None
         assert cluster_slots.get((0, 8191)).get("primary") == ("127.0.0.1", 7000)
 
+    @skip_if_server_version_lt("7.0.0")
     def test_cluster_shards(self, r):
         cluster_shards = r.cluster_shards()
         assert isinstance(cluster_shards, list)
@@ -1172,6 +1176,7 @@ def test_cluster_shards(self, r):
                 for attribute in node.keys():
                     assert attribute in attributes
 
+    @skip_if_server_version_lt("7.2.0")
     def test_cluster_myshardid(self, r):
         myshardid = r.cluster_myshardid()
         assert isinstance(myshardid, str)
@@ -1182,6 +1187,7 @@ def test_cluster_addslots(self, r):
         mock_node_resp(node, "OK")
         assert r.cluster_addslots(node, 1, 2, 3) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_cluster_addslotsrange(self, r):
         node = r.get_random_node()
         mock_node_resp(node, "OK")
@@ -1211,6 +1217,7 @@ def test_cluster_delslots(self):
         assert node0.valkey_connection.connection.read_response.called
         assert node1.valkey_connection.connection.read_response.called
 
+    @skip_if_server_version_lt("7.0.0")
     def test_cluster_delslotsrange(self):
         cluster_slots = [
             [
@@ -1399,6 +1406,7 @@ def test_cluster_replicas(self, r):
             == "r4xfga22229cf3c652b6fca0d09ff69f3e0d4d"
         )
 
+    @skip_if_server_version_lt("7.0.0")
     def test_cluster_links(self, r):
         node = r.get_random_node()
         res = r.cluster_links(node)
@@ -1521,13 +1529,16 @@ def test_time(self, r):
         assert isinstance(t[0], int)
         assert isinstance(t[1], int)
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_usage(self, r):
         r.set("foo", "bar")
         assert isinstance(r.memory_usage("foo"), int)
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_malloc_stats(self, r):
         assert r.memory_malloc_stats()
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_stats(self, r):
         # put a key into the current db to make sure that "db.<current-db>"
         # has data
@@ -1539,10 +1550,12 @@ def test_memory_stats(self, r):
             if key.startswith("db."):
                 assert isinstance(value, dict)
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_help(self, r):
         with pytest.raises(NotImplementedError):
             r.memory_help()
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_doctor(self, r):
         with pytest.raises(NotImplementedError):
             r.memory_doctor()
@@ -1555,6 +1568,7 @@ def test_cluster_echo(self, r):
         node = r.get_primaries()[0]
         assert r.echo("foo bar", target_nodes=node) == b"foo bar"
 
+    @skip_if_server_version_lt("1.0.0")
     def test_debug_segfault(self, r):
         with pytest.raises(NotImplementedError):
             r.debug_segfault()
@@ -1572,12 +1586,14 @@ def test_config_resetstat(self, r):
         )
         assert reset_commands_processed < prior_commands_processed
 
+    @skip_if_server_version_lt("6.2.0")
     def test_client_trackinginfo(self, r):
         node = r.get_primaries()[0]
         res = r.client_trackinginfo(target_nodes=node)
         assert len(res) > 2
         assert "prefixes" in res or b"prefixes" in res
 
+    @skip_if_server_version_lt("2.9.50")
     def test_client_pause(self, r):
         node = r.get_primaries()[0]
         assert r.client_pause(1, target_nodes=node)
@@ -1585,13 +1601,16 @@ def test_client_pause(self, r):
         with pytest.raises(ValkeyError):
             r.client_pause(timeout="not an integer", target_nodes=node)
 
+    @skip_if_server_version_lt("6.2.0")
     def test_client_unpause(self, r):
         assert r.client_unpause()
 
+    @skip_if_server_version_lt("5.0.0")
     def test_client_id(self, r):
         node = r.get_primaries()[0]
         assert r.client_id(target_nodes=node) > 0
 
+    @skip_if_server_version_lt("5.0.0")
     def test_client_unblock(self, r):
         node = r.get_primaries()[0]
         myid = r.client_id(target_nodes=node)
@@ -1599,17 +1618,20 @@ def test_client_unblock(self, r):
         assert not r.client_unblock(myid, error=True, target_nodes=node)
         assert not r.client_unblock(myid, error=False, target_nodes=node)
 
+    @skip_if_server_version_lt("6.0.0")
     def test_client_getredir(self, r):
         node = r.get_primaries()[0]
         assert isinstance(r.client_getredir(target_nodes=node), int)
         assert r.client_getredir(target_nodes=node) == -1
 
+    @skip_if_server_version_lt("6.2.0")
     def test_client_info(self, r):
         node = r.get_primaries()[0]
         info = r.client_info(target_nodes=node)
         assert isinstance(info, dict)
         assert "addr" in info
 
+    @skip_if_server_version_lt("2.6.9")
     def test_client_kill(self, r, r2):
         node = r.get_primaries()[0]
         r.client_setname("valkey-py-c1", target_nodes="all")
@@ -1633,11 +1655,13 @@ def test_client_kill(self, r, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_cluster_bitop_not_empty_string(self, r):
         r["{foo}a"] = ""
         r.bitop("not", "{foo}r", "{foo}a")
         assert r.get("{foo}r") is None
 
+    @skip_if_server_version_lt("2.6.0")
     def test_cluster_bitop_not(self, r):
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -1645,6 +1669,7 @@ def test_cluster_bitop_not(self, r):
         r.bitop("not", "{foo}r", "{foo}a")
         assert int(binascii.hexlify(r["{foo}r"]), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     def test_cluster_bitop_not_in_place(self, r):
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -1652,6 +1677,7 @@ def test_cluster_bitop_not_in_place(self, r):
         r.bitop("not", "{foo}a", "{foo}a")
         assert int(binascii.hexlify(r["{foo}a"]), 16) == correct
 
+    @skip_if_server_version_lt("2.6.0")
     def test_cluster_bitop_single_string(self, r):
         test_str = b"\x01\x02\xFF"
         r["{foo}a"] = test_str
@@ -1662,6 +1688,7 @@ def test_cluster_bitop_single_string(self, r):
         assert r["{foo}res2"] == test_str
         assert r["{foo}res3"] == test_str
 
+    @skip_if_server_version_lt("2.6.0")
     def test_cluster_bitop_string_operands(self, r):
         r["{foo}a"] = b"\x01\x02\xFF\xFF"
         r["{foo}b"] = b"\x01\x02\xFF"
@@ -1672,6 +1699,7 @@ def test_cluster_bitop_string_operands(self, r):
         assert int(binascii.hexlify(r["{foo}res2"]), 16) == 0x0102FFFF
         assert int(binascii.hexlify(r["{foo}res3"]), 16) == 0x000000FF
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_copy(self, r):
         assert r.copy("{foo}a", "{foo}b") == 0
         r.set("{foo}a", "bar")
@@ -1679,17 +1707,20 @@ def test_cluster_copy(self, r):
         assert r.get("{foo}a") == b"bar"
         assert r.get("{foo}b") == b"bar"
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_copy_and_replace(self, r):
         r.set("{foo}a", "foo1")
         r.set("{foo}b", "foo2")
         assert r.copy("{foo}a", "{foo}b") == 0
         assert r.copy("{foo}a", "{foo}b", replace=True) == 1
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_lmove(self, r):
         r.rpush("{foo}a", "one", "two", "three", "four")
         assert r.lmove("{foo}a", "{foo}b")
         assert r.lmove("{foo}a", "{foo}b", "right", "left")
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_blmove(self, r):
         r.rpush("{foo}a", "one", "two", "three", "four")
         assert r.blmove("{foo}a", "{foo}b", 5)
@@ -1850,6 +1881,7 @@ def test_cluster_sunionstore(self, r):
         assert r.sunionstore("{foo}c", "{foo}a", "{foo}b") == 3
         assert r.smembers("{foo}c") == {b"1", b"2", b"3"}
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_zdiff(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         r.zadd("{foo}b", {"a1": 1, "a2": 2})
@@ -1862,6 +1894,7 @@ def test_cluster_zdiff(self, r):
             [[b"a3", 3.0]],
         )
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_zdiffstore(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         r.zadd("{foo}b", {"a1": 1, "a2": 2})
@@ -1870,6 +1903,7 @@ def test_cluster_zdiffstore(self, r):
         response = r.zrange("{foo}out", 0, -1, withscores=True)
         assert_resp_response(r, response, [(b"a3", 3.0)], [[b"a3", 3.0]])
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_zinter(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 1})
         r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2})
@@ -1957,6 +1991,7 @@ def test_cluster_zinterstore_with_weight(self, r):
             [[b"a3", 20.0], [b"a1", 23.0]],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     def test_cluster_bzpopmax(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2})
         r.zadd("{foo}b", {"b1": 10, "b2": 20})
@@ -1993,6 +2028,7 @@ def test_cluster_bzpopmax(self, r):
             [b"{foo}c", b"c1", 100],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     def test_cluster_bzpopmin(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2})
         r.zadd("{foo}b", {"b1": 10, "b2": 20})
@@ -2029,6 +2065,7 @@ def test_cluster_bzpopmin(self, r):
             [b"{foo}c", b"c1", 100],
         )
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_zrangestore(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 2, "a3": 3})
         assert r.zrangestore("{foo}b", "{foo}a", 0, 1)
@@ -2055,6 +2092,7 @@ def test_cluster_zrangestore(self, r):
         )
         assert r.zrange("{foo}b", 0, -1) == [b"a2"]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_zunion(self, r):
         r.zadd("{foo}a", {"a1": 1, "a2": 1, "a3": 1})
         r.zadd("{foo}b", {"a1": 2, "a2": 2, "a3": 2})
@@ -2143,6 +2181,7 @@ def test_cluster_zunionstore_with_weight(self, r):
             [[b"a2", 5.0], [b"a4", 12.0], [b"a3", 20.0], [b"a1", 23.0]],
         )
 
+    @skip_if_server_version_lt("2.8.9")
     def test_cluster_pfcount(self, r):
         members = {b"1", b"2", b"3"}
         r.pfadd("{foo}a", *members)
@@ -2152,6 +2191,7 @@ def test_cluster_pfcount(self, r):
         assert r.pfcount("{foo}b") == len(members_b)
         assert r.pfcount("{foo}a", "{foo}b") == len(members_b.union(members))
 
+    @skip_if_server_version_lt("2.8.9")
     def test_cluster_pfmerge(self, r):
         mema = {b"1", b"2", b"3"}
         memb = {b"2", b"3", b"4"}
@@ -2170,6 +2210,7 @@ def test_cluster_sort_store(self, r):
         assert r.lrange("{foo}sorted_values", 0, -1) == [b"1", b"2", b"3"]
 
     # GEO COMMANDS
+    @skip_if_server_version_lt("6.2.0")
     def test_cluster_geosearchstore(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2188,6 +2229,7 @@ def test_cluster_geosearchstore(self, r):
         assert r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearchstore_dist(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2207,6 +2249,7 @@ def test_geosearchstore_dist(self, r):
         # instead of save the geo score, the distance is saved.
         assert r.zscore("{foo}places_barcelona", "place1") == 88.05060698409301
 
+    @skip_if_server_version_lt("3.2.0")
     def test_cluster_georadius_store(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2221,6 +2264,7 @@ def test_cluster_georadius_store(self, r):
         assert r.zrange("{foo}places_barcelona", 0, -1) == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     def test_cluster_georadius_store_dist(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -2253,6 +2297,7 @@ def test_cluster_keys(self, r):
         assert set(r.keys(pattern="test*", target_nodes="primaries")) == keys
 
     # SCAN COMMANDS
+    @skip_if_server_version_lt("2.8.0")
     def test_cluster_scan(self, r):
         r.set("a", 1)
         r.set("b", 2)
@@ -2271,6 +2316,7 @@ def test_cluster_scan(self, r):
             assert sorted(cursors.keys()) == sorted(node.name for node in nodes)
             assert all(cursor == 0 for cursor in cursors.values())
 
+    @skip_if_server_version_lt("6.0.0")
     def test_cluster_scan_type(self, r):
         r.sadd("a-set", 1)
         r.sadd("b-set", 1)
@@ -2291,6 +2337,7 @@ def test_cluster_scan_type(self, r):
             assert sorted(cursors.keys()) == sorted(node.name for node in nodes)
             assert all(cursor == 0 for cursor in cursors.values())
 
+    @skip_if_server_version_lt("2.8.0")
     def test_cluster_scan_iter(self, r):
         keys_all = []
         keys_1 = []
@@ -2317,6 +2364,7 @@ def test_cluster_randomkey(self, r):
             r[key] = 1
         assert r.randomkey(target_nodes=node) in (b"{foo}a", b"{foo}b", b"{foo}c")
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_log(self, r, request):
         key = "{cache}:"
         node = r.get_node_from_key(key)
@@ -2370,6 +2418,7 @@ def try_delete_libs(self, r, *lib_names):
             except Exception:
                 pass
 
+    @skip_if_server_version_lt("7.1.140")
     @pytest.mark.skip
     def test_tfunction_load_delete(self, r):
         r.gears_refresh_cluster()
@@ -2378,6 +2427,7 @@ def test_tfunction_load_delete(self, r):
         assert r.tfunction_load(lib_code)
         assert r.tfunction_delete("lib1")
 
+    @skip_if_server_version_lt("7.1.140")
     @pytest.mark.skip
     def test_tfunction_list(self, r):
         r.gears_refresh_cluster()
@@ -2401,6 +2451,7 @@ def test_tfunction_list(self, r):
         assert r.tfunction_delete("lib2")
         assert r.tfunction_delete("lib3")
 
+    @skip_if_server_version_lt("7.1.140")
     @pytest.mark.skip
     def test_tfcall(self, r):
         r.gears_refresh_cluster()
diff --git a/tests/test_command_parser.py b/tests/test_command_parser.py
index 4165d757..ae2fa8d6 100644
--- a/tests/test_command_parser.py
+++ b/tests/test_command_parser.py
@@ -1,7 +1,7 @@
 import pytest
 from valkey._parsers import CommandsParser
 
-from .conftest import assert_resp_response
+from .conftest import assert_resp_response, skip_if_server_version_lt
 
 
 class TestCommandsParser:
@@ -85,6 +85,7 @@ def test_get_moveable_keys(self, r):
         )
 
     # A bug in redis<7.0 causes this to fail: https://github.com/redis/redis/issues/9493
+    @skip_if_server_version_lt("7.0.0")
     def test_get_eval_keys_with_0_keys(self, r):
         commands_parser = CommandsParser(r)
         args = ["EVAL", "return {ARGV[1],ARGV[2]}", 0, "key1", "key2"]
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 8390bf4d..9f35b5d7 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -24,6 +24,7 @@
     assert_resp_response,
     assert_resp_response_in,
     is_resp2_connection,
+    skip_if_server_version_gte,
     skip_if_server_version_lt,
     skip_unless_arch_bits,
 )
@@ -135,16 +136,19 @@ def test_command_on_invalid_key_type(self, r):
             r["a"]
 
     # SERVER INFORMATION
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_cat_no_category(self, r):
         categories = r.acl_cat()
         assert isinstance(categories, list)
         assert "read" in categories or b"read" 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
 
+    @skip_if_server_version_lt("7.0.0")
     def test_acl_dryrun(self, r, request):
         username = "valkey-py-user"
 
@@ -158,6 +162,7 @@ def teardown():
         no_permissions_message = b"user has no permissions to run the"
         assert no_permissions_message in r.acl_dryrun(username, "get", "key")
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_deluser(self, r, request):
         username = "valkey-py-user"
 
@@ -181,6 +186,7 @@ def teardown():
         assert r.acl_getuser(users[3]) is None
         assert r.acl_getuser(users[4]) is None
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_genpass(self, r):
         password = r.acl_genpass()
         assert isinstance(password, (str, bytes))
@@ -194,6 +200,7 @@ def test_acl_genpass(self, r):
         assert isinstance(password, (str, bytes))
         assert len(password) == 139
 
+    @skip_if_server_version_lt("7.0.0")
     def test_acl_getuser_setuser(self, r, request):
         r.flushall()
         username = "valkey-py-user"
@@ -326,11 +333,13 @@ def teardown():
             [{"commands": "-@all +set", "keys": "%W~app*", "channels": ""}],
         )
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_help(self, r):
         res = r.acl_help()
         assert isinstance(res, list)
         assert len(res) != 0
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_list(self, r, request):
         username = "valkey-py-user"
         start = r.acl_list()
@@ -344,6 +353,7 @@ def teardown():
         users = r.acl_list()
         assert len(users) == len(start) + 1
 
+    @skip_if_server_version_lt("6.0.0")
     @pytest.mark.onlynoncluster
     def test_acl_log(self, r, request):
         username = "valkey-py-user"
@@ -390,6 +400,7 @@ def teardown():
             expected.keys(),
         )
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_setuser_categories_without_prefix_fails(self, r, request):
         username = "valkey-py-user"
 
@@ -401,6 +412,7 @@ def teardown():
         with pytest.raises(exceptions.DataError):
             r.acl_setuser(username, categories=["list"])
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_setuser_commands_without_prefix_fails(self, r, request):
         username = "valkey-py-user"
 
@@ -412,6 +424,7 @@ def teardown():
         with pytest.raises(exceptions.DataError):
             r.acl_setuser(username, commands=["get"])
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_setuser_add_passwords_and_nopass_fails(self, r, request):
         username = "valkey-py-user"
 
@@ -423,11 +436,13 @@ def teardown():
         with pytest.raises(exceptions.DataError):
             r.acl_setuser(username, passwords="+mypass", nopass=True)
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_users(self, r):
         users = r.acl_users()
         assert isinstance(users, list)
         assert len(users) > 0
 
+    @skip_if_server_version_lt("6.0.0")
     def test_acl_whoami(self, r):
         username = r.acl_whoami()
         assert isinstance(username, (str, bytes))
@@ -439,12 +454,14 @@ def test_client_list(self, r):
         assert "addr" in clients[0]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_client_info(self, r):
         info = r.client_info()
         assert isinstance(info, dict)
         assert "addr" in info
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("5.0.0")
     def test_client_list_types_not_replica(self, r):
         with pytest.raises(exceptions.ValkeyError):
             r.client_list(_type="not a client type")
@@ -457,6 +474,7 @@ def test_client_list_replica(self, r):
         assert isinstance(clients, list)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_client_list_client_id(self, r, request):
         clients = r.client_list()
         clients = r.client_list(client_id=[clients[0]["id"]])
@@ -471,16 +489,19 @@ def test_client_list_client_id(self, r, request):
         assert len(clients_listed) > 1
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("5.0.0")
     def test_client_id(self, r):
         assert r.client_id() > 0
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_client_trackinginfo(self, r):
         res = r.client_trackinginfo()
         assert len(res) > 2
         assert "prefixes" in res or b"prefixes" in res
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.0.0")
     def test_client_tracking(self, r, r2):
         # simple case
         assert r.client_tracking_on()
@@ -501,6 +522,7 @@ def test_client_tracking(self, r, r2):
             assert r.client_tracking_on(prefix=["foo", "bar", "blee"])
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("5.0.0")
     def test_client_unblock(self, r):
         myid = r.client_id()
         assert not r.client_unblock(myid)
@@ -508,14 +530,17 @@ def test_client_unblock(self, r):
         assert not r.client_unblock(myid, error=False)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.9")
     def test_client_getname(self, r):
         assert r.client_getname() is None
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.9")
     def test_client_setname(self, r):
         assert r.client_setname("valkey_py_test")
         assert_resp_response(r, r.client_getname(), "valkey_py_test", b"valkey_py_test")
 
+    @skip_if_server_version_lt("7.2.0")
     def test_client_setinfo(self, r: valkey.Valkey):
         r.ping()
         info = r.client_info()
@@ -536,6 +561,7 @@ def test_client_setinfo(self, r: valkey.Valkey):
         assert info["lib-ver"] == ""
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.9")
     def test_client_kill(self, r, r2):
         r.client_setname("valkey-py-c1")
         r2.client_setname("valkey-py-c2")
@@ -559,6 +585,7 @@ def test_client_kill(self, r, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.8.12")
     def test_client_kill_filter_invalid_params(self, r):
         # empty
         with pytest.raises(exceptions.DataError):
@@ -573,6 +600,7 @@ def test_client_kill_filter_invalid_params(self, r):
             r.client_kill_filter(_type="caster")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.12")
     def test_client_kill_filter_by_id(self, r, r2):
         r.client_setname("valkey-py-c1")
         r2.client_setname("valkey-py-c2")
@@ -598,6 +626,7 @@ def test_client_kill_filter_by_id(self, r, r2):
         assert clients[0].get("name") == "valkey-py-c1"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.12")
     def test_client_kill_filter_by_addr(self, r, r2):
         r.client_setname("valkey-py-c1")
         r2.client_setname("valkey-py-c2")
@@ -622,12 +651,14 @@ def test_client_kill_filter_by_addr(self, r, r2):
         assert len(clients) == 1
         assert clients[0].get("name") == "valkey-py-c1"
 
+    @skip_if_server_version_lt("2.6.9")
     def test_client_list_after_client_setname(self, r):
         r.client_setname("valkey_py_test")
         clients = r.client_list()
         # we don't know which client ours will be
         assert "valkey_py_test" in [c["name"] for c in clients]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_client_kill_filter_by_laddr(self, r, r2):
         r.client_setname("valkey-py-c1")
         r2.client_setname("valkey-py-c2")
@@ -643,6 +674,7 @@ def test_client_kill_filter_by_laddr(self, r, r2):
         client_2_addr = clients_by_name["valkey-py-c2"].get("laddr")
         assert r.client_kill_filter(laddr=client_2_addr)
 
+    @skip_if_server_version_lt("6.0.0")
     def test_client_kill_filter_by_user(self, r, request):
         killuser = "user_to_kill"
         r.acl_setuser(
@@ -669,12 +701,14 @@ def test_client_kill_filter_by_maxage(self, r, request):
         assert len(r.client_list()) == 1
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.9.50")
     def test_client_pause(self, r):
         assert r.client_pause(1)
         assert r.client_pause(timeout=1)
         with pytest.raises(exceptions.ValkeyError):
             r.client_pause(timeout="not an integer")
 
+    @skip_if_server_version_lt("6.2.0")
     def test_client_pause_all(self, r, r2):
         assert r.client_pause(1, all=False)
         assert r2.set("foo", "bar")
@@ -682,16 +716,19 @@ def test_client_pause_all(self, r, r2):
         assert r.get("foo") == b"bar"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_client_unpause(self, r):
         assert r.client_unpause() == b"OK"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_client_no_evict(self, r):
         assert r.client_no_evict("ON")
         with pytest.raises(TypeError):
             r.client_no_evict()
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.2.0")
     def test_client_no_touch(self, r):
         assert r.client_no_touch("ON") == b"OK"
         assert r.client_no_touch("OFF") == b"OK"
@@ -699,6 +736,7 @@ def test_client_no_touch(self, r):
             r.client_no_touch()
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.2.0")
     def test_client_reply(self, r, r_timeout):
         assert r_timeout.client_reply("ON") == b"OK"
         with pytest.raises(exceptions.ValkeyError):
@@ -712,10 +750,12 @@ def test_client_reply(self, r, r_timeout):
         assert r.get("foo") == b"bar"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.0.0")
     def test_client_getredir(self, r):
         assert isinstance(r.client_getredir(), int)
         assert r.client_getredir() == -1
 
+    @skip_if_server_version_lt("6.0.0")
     def test_hello_notI_implemented(self, r):
         with pytest.raises(NotImplementedError):
             r.hello()
@@ -726,6 +766,7 @@ def test_config_get(self, r):
         # # assert 'maxmemory' in data
         # assert data['maxmemory'].isdigit()
 
+    @skip_if_server_version_lt("7.0.0")
     def test_config_get_multi_params(self, r: valkey.Valkey):
         res = r.config_get("*max-*-entries*", "maxmemory")
         assert "maxmemory" in res
@@ -746,6 +787,7 @@ def test_config_set(self, r):
         assert r.config_set("timeout", 0)
         assert r.config_get()["timeout"] == "0"
 
+    @skip_if_server_version_lt("7.0.0")
     def test_config_set_multi_params(self, r: valkey.Valkey):
         r.config_set("timeout", 70, "maxmemory", 100)
         assert r.config_get()["timeout"] == "70"
@@ -754,6 +796,7 @@ def test_config_set_multi_params(self, r: valkey.Valkey):
         assert r.config_get()["timeout"] == "0"
         assert r.config_get()["maxmemory"] == "0"
 
+    @skip_if_server_version_lt("6.0.0")
     def test_failover(self, r):
         with pytest.raises(NotImplementedError):
             r.failover()
@@ -778,6 +821,7 @@ def test_info(self, r):
         assert "valkey_version" in info.keys()
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_info_multi_sections(self, r):
         res = r.info("clients", "server")
         assert isinstance(res, dict)
@@ -789,6 +833,7 @@ def test_lastsave(self, r):
         assert isinstance(r.lastsave(), datetime.datetime)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("5.0.0")
     def test_lolwut(self, r):
         lolwut = r.lolwut().decode("utf-8")
         assert "Redis ver." in lolwut
@@ -797,6 +842,7 @@ def test_lolwut(self, r):
         assert "Redis ver." in lolwut
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_reset(self, r):
         assert_resp_response(r, r.reset(), "RESET", b"RESET")
 
@@ -814,6 +860,7 @@ def test_ping(self, r):
     def test_quit(self, r):
         assert r.quit()
 
+    @skip_if_server_version_lt("2.8.12")
     @pytest.mark.onlynoncluster
     def test_role(self, r):
         assert r.role()[0] == b"master"
@@ -892,6 +939,7 @@ def test_slowlog_length(self, r, slowlog):
         r.get("foo")
         assert isinstance(r.slowlog_len(), int)
 
+    @skip_if_server_version_lt("2.6.0")
     def test_time(self, r):
         t = r.time()
         assert len(t) == 2
@@ -920,6 +968,7 @@ def test_append(self, r):
         assert r.append("a", "a2") == 4
         assert r["a"] == b"a1a2"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_bitcount(self, r):
         r.setbit("a", 5, True)
         assert r.bitcount("a") == 1
@@ -938,6 +987,7 @@ def test_bitcount(self, r):
         assert r.bitcount("a", -2, -1) == 2
         assert r.bitcount("a", 1, 1) == 1
 
+    @skip_if_server_version_lt("7.0.0")
     def test_bitcount_mode(self, r):
         r.set("mykey", "foobar")
         assert r.bitcount("mykey") == 26
@@ -947,12 +997,14 @@ def test_bitcount_mode(self, r):
             assert r.bitcount("mykey", 5, 30, "but")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.0")
     def test_bitop_not_empty_string(self, r):
         r["a"] = ""
         r.bitop("not", "r", "a")
         assert r.get("r") is None
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.0")
     def test_bitop_not(self, r):
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -961,6 +1013,7 @@ def test_bitop_not(self, r):
         assert int(binascii.hexlify(r["r"]), 16) == correct
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.0")
     def test_bitop_not_in_place(self, r):
         test_str = b"\xAA\x00\xFF\x55"
         correct = ~0xAA00FF55 & 0xFFFFFFFF
@@ -969,6 +1022,7 @@ def test_bitop_not_in_place(self, r):
         assert int(binascii.hexlify(r["a"]), 16) == correct
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.0")
     def test_bitop_single_string(self, r):
         test_str = b"\x01\x02\xFF"
         r["a"] = test_str
@@ -980,6 +1034,7 @@ def test_bitop_single_string(self, r):
         assert r["res3"] == test_str
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.6.0")
     def test_bitop_string_operands(self, r):
         r["a"] = b"\x01\x02\xFF\xFF"
         r["b"] = b"\x01\x02\xFF"
@@ -991,6 +1046,7 @@ def test_bitop_string_operands(self, r):
         assert int(binascii.hexlify(r["res3"]), 16) == 0x000000FF
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.7")
     def test_bitpos(self, r):
         key = "key:bitpos"
         r.set(key, b"\xff\xf0\x00")
@@ -1003,6 +1059,7 @@ def test_bitpos(self, r):
         r.set(key, b"\x00\x00\x00")
         assert r.bitpos(key, 1) == -1
 
+    @skip_if_server_version_lt("2.8.7")
     def test_bitpos_wrong_arguments(self, r):
         key = "key:bitpos:wrong:args"
         r.set(key, b"\xff\xf0\x00")
@@ -1011,6 +1068,7 @@ def test_bitpos_wrong_arguments(self, r):
         with pytest.raises(exceptions.ValkeyError):
             r.bitpos(key, 7) == 12
 
+    @skip_if_server_version_lt("7.0.0")
     def test_bitpos_mode(self, r):
         r.set("mykey", b"\x00\xff\xf0")
         assert r.bitpos("mykey", 1, 0) == 8
@@ -1020,6 +1078,7 @@ def test_bitpos_mode(self, r):
             r.bitpos("mykey", 1, 7, 15, "bite")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_copy(self, r):
         assert r.copy("a", "b") == 0
         r.set("a", "foo")
@@ -1028,6 +1087,7 @@ def test_copy(self, r):
         assert r.get("b") == b"foo"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_copy_and_replace(self, r):
         r.set("a", "foo1")
         r.set("b", "foo2")
@@ -1035,6 +1095,7 @@ def test_copy_and_replace(self, r):
         assert r.copy("a", "b", replace=True) == 1
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_copy_to_another_database(self, request):
         r0 = _get_client(valkey.Valkey, request, db=0)
         r1 = _get_client(valkey.Valkey, request, db=1)
@@ -1072,12 +1133,14 @@ def test_delitem(self, r):
         del r["a"]
         assert r.get("a") is None
 
+    @skip_if_server_version_lt("4.0.0")
     def test_unlink(self, r):
         assert r.unlink("a") == 0
         r["a"] = "foo"
         assert r.unlink("a") == 1
         assert r.get("a") is None
 
+    @skip_if_server_version_lt("4.0.0")
     def test_unlink_with_multiple_keys(self, r):
         r["a"] = "foo"
         r["b"] = "bar"
@@ -1086,6 +1149,7 @@ def test_unlink_with_multiple_keys(self, r):
         assert r.get("b") is None
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_lcs(self, r):
         r.mset({"foo": "ohmytext", "bar": "mynewtext"})
         assert r.lcs("foo", "bar") == b"mytext"
@@ -1099,6 +1163,7 @@ def test_lcs(self, r):
         with pytest.raises(valkey.ResponseError):
             assert r.lcs("foo", "bar", len=True, idx=True)
 
+    @skip_if_server_version_lt("2.6.0")
     def test_dump_and_restore(self, r):
         r["a"] = "foo"
         dumped = r.dump("a")
@@ -1106,6 +1171,7 @@ def test_dump_and_restore(self, r):
         r.restore("a", 0, dumped)
         assert r["a"] == b"foo"
 
+    @skip_if_server_version_lt("3.0.0")
     def test_dump_and_restore_and_replace(self, r):
         r["a"] = "bar"
         dumped = r.dump("a")
@@ -1115,6 +1181,7 @@ def test_dump_and_restore_and_replace(self, r):
         r.restore("a", 0, dumped, replace=True)
         assert r["a"] == b"bar"
 
+    @skip_if_server_version_lt("5.0.0")
     def test_dump_and_restore_absttl(self, r):
         r["a"] = "foo"
         dumped = r.dump("a")
@@ -1146,22 +1213,26 @@ def test_expire(self, r):
         assert r.persist("a")
         assert r.ttl("a") == -1
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expire_option_nx(self, r):
         r.set("key", "val")
         assert r.expire("key", 100, nx=True) == 1
         assert r.expire("key", 500, nx=True) == 0
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expire_option_xx(self, r):
         r.set("key", "val")
         assert r.expire("key", 100, xx=True) == 0
         assert r.expire("key", 100)
         assert r.expire("key", 500, xx=True) == 1
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expire_option_gt(self, r):
         r.set("key", "val", 100)
         assert r.expire("key", 50, gt=True) == 0
         assert r.expire("key", 500, gt=True) == 1
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expire_option_lt(self, r):
         r.set("key", "val", 100)
         assert r.expire("key", 50, lt=True) == 1
@@ -1184,11 +1255,13 @@ def test_expireat_unixtime(self, r):
         assert r.expireat("a", expire_at_seconds) is True
         assert 0 < r.ttl("a") <= 61
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expiretime(self, r):
         r.set("a", "foo")
         r.expireat("a", 33177117420)
         assert r.expiretime("a") == 33177117420
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expireat_option_nx(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
@@ -1196,6 +1269,7 @@ def test_expireat_option_nx(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
         assert r.expireat("key", expire_at, nx=True) is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expireat_option_xx(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
@@ -1204,6 +1278,7 @@ def test_expireat_option_xx(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
         assert r.expireat("key", expire_at, xx=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expireat_option_gt(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
         assert r.set("key", "val") is True
@@ -1213,6 +1288,7 @@ def test_expireat_option_gt(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=3)
         assert r.expireat("key", expire_at, gt=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_expireat_option_lt(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
         assert r.set("key", "val") is True
@@ -1235,12 +1311,14 @@ def test_get_and_set(self, r):
         assert r.get("integer") == str(integer).encode()
         assert r.get("unicode_string").decode("utf-8") == unicode_string
 
+    @skip_if_server_version_lt("6.2.0")
     def test_getdel(self, r):
         assert r.getdel("a") is None
         r.set("a", 1)
         assert r.getdel("a") == b"1"
         assert r.getdel("a") is None
 
+    @skip_if_server_version_lt("6.2.0")
     def test_getex(self, r):
         r.set("a", 1)
         assert r.getex("a") == b"1"
@@ -1307,6 +1385,7 @@ def test_incrby(self, r):
         assert r.incrby("a", 4) == 5
         assert r["a"] == b"5"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_incrbyfloat(self, r):
         assert r.incrbyfloat("a") == 1.0
         assert r["a"] == b"1"
@@ -1333,12 +1412,14 @@ def test_mget(self, r):
         assert r.mget("a", "other", "b", "c") == [b"1", None, b"2", b"3"]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_lmove(self, r):
         r.rpush("a", "one", "two", "three", "four")
         assert r.lmove("a", "b")
         assert r.lmove("a", "b", "right", "left")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_blmove(self, r):
         r.rpush("a", "one", "two", "three", "four")
         assert r.blmove("a", "b", 5)
@@ -1361,6 +1442,7 @@ def test_msetnx(self, r):
             assert r[k] == v
         assert r.get("d") is None
 
+    @skip_if_server_version_lt("2.6.0")
     def test_pexpire(self, r):
         assert r.pexpire("a", 60000) is False
         r["a"] = "foo"
@@ -1369,39 +1451,46 @@ def test_pexpire(self, r):
         assert r.persist("a")
         assert r.pttl("a") == -1
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpire_option_nx(self, r):
         assert r.set("key", "val") is True
         assert r.pexpire("key", 60000, nx=True) is True
         assert r.pexpire("key", 60000, nx=True) is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpire_option_xx(self, r):
         assert r.set("key", "val") is True
         assert r.pexpire("key", 60000, xx=True) is False
         assert r.pexpire("key", 60000) is True
         assert r.pexpire("key", 70000, xx=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpire_option_gt(self, r):
         assert r.set("key", "val") is True
         assert r.pexpire("key", 60000) is True
         assert r.pexpire("key", 70000, gt=True) is True
         assert r.pexpire("key", 50000, gt=True) is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpire_option_lt(self, r):
         assert r.set("key", "val") is True
         assert r.pexpire("key", 60000) is True
         assert r.pexpire("key", 50000, lt=True) is True
         assert r.pexpire("key", 70000, lt=True) is False
 
+    @skip_if_server_version_lt("2.6.0")
     def test_pexpireat_datetime(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
         r["a"] = "foo"
         assert r.pexpireat("a", expire_at) is True
         assert 0 < r.pttl("a") <= 61000
 
+    @skip_if_server_version_lt("2.6.0")
     def test_pexpireat_no_key(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
         assert r.pexpireat("a", expire_at) is False
 
+    @skip_if_server_version_lt("2.6.0")
     def test_pexpireat_unixtime(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
         r["a"] = "foo"
@@ -1409,12 +1498,14 @@ def test_pexpireat_unixtime(self, r):
         assert r.pexpireat("a", expire_at_milliseconds) is True
         assert 0 < r.pttl("a") <= 61000
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpireat_option_nx(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
         assert r.pexpireat("key", expire_at, nx=True) is True
         assert r.pexpireat("key", expire_at, nx=True) is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpireat_option_xx(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
@@ -1422,6 +1513,7 @@ def test_pexpireat_option_xx(self, r):
         assert r.pexpireat("key", expire_at) is True
         assert r.pexpireat("key", expire_at, xx=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpireat_option_gt(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
@@ -1431,6 +1523,7 @@ def test_pexpireat_option_gt(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=3)
         assert r.pexpireat("key", expire_at, gt=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpireat_option_lt(self, r):
         assert r.set("key", "val") is True
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=2)
@@ -1440,22 +1533,26 @@ def test_pexpireat_option_lt(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(minutes=1)
         assert r.pexpireat("key", expire_at, lt=True) is True
 
+    @skip_if_server_version_lt("7.0.0")
     def test_pexpiretime(self, r):
         r.set("a", "foo")
         r.pexpireat("a", 33177117420000)
         assert r.pexpiretime("a") == 33177117420000
 
+    @skip_if_server_version_lt("2.6.0")
     def test_psetex(self, r):
         assert r.psetex("a", 1000, "value")
         assert r["a"] == b"value"
         assert 0 < r.pttl("a") <= 1000
 
+    @skip_if_server_version_lt("2.6.0")
     def test_psetex_timedelta(self, r):
         expire_at = datetime.timedelta(milliseconds=1000)
         assert r.psetex("a", expire_at, "value")
         assert r["a"] == b"value"
         assert 0 < r.pttl("a") <= 1000
 
+    @skip_if_server_version_lt("2.6.0")
     def test_pttl(self, r):
         assert r.pexpire("a", 10000) is False
         r["a"] = "1"
@@ -1464,10 +1561,12 @@ def test_pttl(self, r):
         assert r.persist("a")
         assert r.pttl("a") == -1
 
+    @skip_if_server_version_lt("2.8.0")
     def test_pttl_no_key(self, r):
         "PTTL on servers 2.8 and after return -2 when the key doesn't exist"
         assert r.pttl("a") == -2
 
+    @skip_if_server_version_lt("6.2.0")
     def test_hrandfield(self, r):
         assert r.hrandfield("key") is None
         r.hset("key", mapping={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5})
@@ -1502,11 +1601,13 @@ def test_renamenx(self, r):
         assert r["a"] == b"1"
         assert r["b"] == b"2"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_nx(self, r):
         assert r.set("a", "1", nx=True)
         assert not r.set("a", "2", nx=True)
         assert r["a"] == b"1"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_xx(self, r):
         assert not r.set("a", "1", xx=True)
         assert r.get("a") is None
@@ -1514,6 +1615,7 @@ def test_set_xx(self, r):
         assert r.set("a", "2", xx=True)
         assert r.get("a") == b"2"
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_px(self, r):
         assert r.set("a", "1", px=10000)
         assert r["a"] == b"1"
@@ -1522,44 +1624,52 @@ def test_set_px(self, r):
         with pytest.raises(exceptions.DataError):
             assert r.set("a", "1", px=10.0)
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_px_timedelta(self, r):
         expire_at = datetime.timedelta(milliseconds=1000)
         assert r.set("a", "1", px=expire_at)
         assert 0 < r.pttl("a") <= 1000
         assert 0 < r.ttl("a") <= 1
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_ex(self, r):
         assert r.set("a", "1", ex=10)
         assert 0 < r.ttl("a") <= 10
         with pytest.raises(exceptions.DataError):
             assert r.set("a", "1", ex=10.0)
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_ex_str(self, r):
         assert r.set("a", "1", ex="10")
         assert 0 < r.ttl("a") <= 10
         with pytest.raises(exceptions.DataError):
             assert r.set("a", "1", ex="10.5")
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_ex_timedelta(self, r):
         expire_at = datetime.timedelta(seconds=60)
         assert r.set("a", "1", ex=expire_at)
         assert 0 < r.ttl("a") <= 60
 
+    @skip_if_server_version_lt("6.2.0")
     def test_set_exat_timedelta(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(seconds=10)
         assert r.set("a", "1", exat=expire_at)
         assert 0 < r.ttl("a") <= 10
 
+    @skip_if_server_version_lt("6.2.0")
     def test_set_pxat_timedelta(self, r):
         expire_at = valkey_server_time(r) + datetime.timedelta(seconds=50)
         assert r.set("a", "1", pxat=expire_at)
         assert 0 < r.ttl("a") <= 100
 
+    @skip_if_server_version_lt("2.6.0")
     def test_set_multipleoptions(self, r):
         r["a"] = "val"
         assert r.set("a", "1", xx=True, px=10000)
         assert 0 < r.ttl("a") <= 10
 
+    @skip_if_server_version_lt("6.0.0")
     def test_set_keepttl(self, r):
         r["a"] = "val"
         assert r.set("a", "1", xx=True, px=10000)
@@ -1568,6 +1678,7 @@ def test_set_keepttl(self, r):
         assert r.get("a") == b"2"
         assert 0 < r.ttl("a") <= 10
 
+    @skip_if_server_version_lt("6.2.0")
     def test_set_get(self, r):
         assert r.set("a", "True", get=True) is None
         assert r.set("a", "True", get=True) == b"True"
@@ -1593,6 +1704,55 @@ def test_setrange(self, r):
         assert r.setrange("a", 6, "12345") == 11
         assert r["a"] == b"abcdef12345"
 
+    @skip_if_server_version_lt("6.0.0")
+    @skip_if_server_version_gte("7.0.0")
+    def test_stralgo_lcs(self, r):
+        key1 = "{foo}key1"
+        key2 = "{foo}key2"
+        value1 = "ohmytext"
+        value2 = "mynewtext"
+        res = "mytext"
+
+        # test LCS of strings
+        assert r.stralgo("LCS", value1, value2) == res
+        # test using keys
+        r.mset({key1: value1, key2: value2})
+        assert r.stralgo("LCS", key1, key2, specific_argument="keys") == res
+        # test other labels
+        assert r.stralgo("LCS", value1, value2, len=True) == len(res)
+        assert_resp_response(
+            r,
+            r.stralgo("LCS", value1, value2, idx=True),
+            {"len": len(res), "matches": [[(4, 7), (5, 8)], [(2, 3), (0, 1)]]},
+            {"len": len(res), "matches": [[[4, 7], [5, 8]], [[2, 3], [0, 1]]]},
+        )
+        assert_resp_response(
+            r,
+            r.stralgo("LCS", value1, value2, idx=True, withmatchlen=True),
+            {"len": len(res), "matches": [[4, (4, 7), (5, 8)], [2, (2, 3), (0, 1)]]},
+            {"len": len(res), "matches": [[[4, 7], [5, 8], 4], [[2, 3], [0, 1], 2]]},
+        )
+        assert_resp_response(
+            r,
+            r.stralgo(
+                "LCS", value1, value2, idx=True, withmatchlen=True, minmatchlen=4
+            ),
+            {"len": len(res), "matches": [[4, (4, 7), (5, 8)]]},
+            {"len": len(res), "matches": [[[4, 7], [5, 8], 4]]},
+        )
+
+    @skip_if_server_version_lt("6.0.0")
+    @skip_if_server_version_gte("7.0.0")
+    def test_stralgo_negative(self, r):
+        with pytest.raises(exceptions.DataError):
+            r.stralgo("ISSUB", "value1", "value2")
+        with pytest.raises(exceptions.DataError):
+            r.stralgo("LCS", "value1", "value2", len=True, idx=True)
+        with pytest.raises(exceptions.DataError):
+            r.stralgo("LCS", "value1", "value2", specific_argument="INT")
+        with pytest.raises(ValueError):
+            r.stralgo("LCS", "value1", "value2", idx=True, minmatchlen="one")
+
     def test_strlen(self, r):
         r["a"] = "foo"
         assert r.strlen("a") == 3
@@ -1663,6 +1823,7 @@ def test_ttl(self, r):
         assert r.persist("a")
         assert r.ttl("a") == -1
 
+    @skip_if_server_version_lt("2.8.0")
     def test_ttl_nokey(self, r):
         "TTL on servers 2.8 and after return -2 when the key doesn't exist"
         assert r.ttl("a") == -2
@@ -1738,6 +1899,7 @@ def test_brpoplpush_empty_string(self, r):
         assert r.brpoplpush("a", "b") == b""
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_blmpop(self, r):
         r.rpush("a", "1", "2", "3", "4", "5")
         res = [b"a", [b"1", b"2"]]
@@ -1749,6 +1911,7 @@ def test_blmpop(self, r):
         assert r.blmpop(1, "2", "foo", "bar", direction="RIGHT") is None
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_lmpop(self, r):
         r.rpush("foo", "1", "2", "3", "4", "5")
         result = [b"foo", [b"1", b"2"]]
@@ -1782,6 +1945,7 @@ def test_lpop(self, r):
         assert r.lpop("a") == b"3"
         assert r.lpop("a") is None
 
+    @skip_if_server_version_lt("6.2.0")
     def test_lpop_count(self, r):
         r.rpush("a", "1", "2", "3")
         assert r.lpop("a", 2) == [b"1", b"2"]
@@ -1802,6 +1966,7 @@ def test_lpushx(self, r):
         assert r.lpushx("a", "4") == 4
         assert r.lrange("a", 0, -1) == [b"4", b"1", b"2", b"3"]
 
+    @skip_if_server_version_lt("4.0.0")
     def test_lpushx_with_list(self, r):
         # now with a list
         r.lpush("somekey", "a")
@@ -1846,6 +2011,7 @@ def test_rpop(self, r):
         assert r.rpop("a") == b"1"
         assert r.rpop("a") is None
 
+    @skip_if_server_version_lt("6.2.0")
     def test_rpop_count(self, r):
         r.rpush("a", "1", "2", "3")
         assert r.rpop("a", 2) == [b"3", b"2"]
@@ -1867,6 +2033,7 @@ def test_rpush(self, r):
         assert r.rpush("a", "3", "4") == 4
         assert r.lrange("a", 0, -1) == [b"1", b"2", b"3", b"4"]
 
+    @skip_if_server_version_lt("6.0.6")
     def test_lpos(self, r):
         assert r.rpush("a", "a", "b", "c", "1", "2", "3", "c", "c") == 8
         assert r.lpos("a", "a") == 0
@@ -1907,6 +2074,7 @@ def test_rpushx(self, r):
 
     # SCAN COMMANDS
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     def test_scan(self, r):
         r.set("a", 1)
         r.set("b", 2)
@@ -1918,6 +2086,7 @@ def test_scan(self, r):
         assert set(keys) == {b"a"}
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.0.0")
     def test_scan_type(self, r):
         r.sadd("a-set", 1)
         r.hset("a-hash", "foo", 2)
@@ -1926,6 +2095,7 @@ def test_scan_type(self, r):
         assert set(keys) == {b"a-set"}
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     def test_scan_iter(self, r):
         r.set("a", 1)
         r.set("b", 2)
@@ -1935,6 +2105,7 @@ def test_scan_iter(self, r):
         keys = list(r.scan_iter(match="a"))
         assert set(keys) == {b"a"}
 
+    @skip_if_server_version_lt("2.8.0")
     def test_sscan(self, r):
         r.sadd("a", 1, 2, 3)
         cursor, members = r.sscan("a")
@@ -1943,6 +2114,7 @@ def test_sscan(self, r):
         _, members = r.sscan("a", match=b"1")
         assert set(members) == {b"1"}
 
+    @skip_if_server_version_lt("2.8.0")
     def test_sscan_iter(self, r):
         r.sadd("a", 1, 2, 3)
         members = list(r.sscan_iter("a"))
@@ -1950,6 +2122,7 @@ def test_sscan_iter(self, r):
         members = list(r.sscan_iter("a", match=b"1"))
         assert set(members) == {b"1"}
 
+    @skip_if_server_version_lt("2.8.0")
     def test_hscan(self, r):
         r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
         cursor, dic = r.hscan("a")
@@ -1971,6 +2144,7 @@ def test_hscan_novalues(self, r):
         _, keys = r.hscan("a_notset", no_values=True)
         assert keys == []
 
+    @skip_if_server_version_lt("2.8.0")
     def test_hscan_iter(self, r):
         r.hset("a", mapping={"a": 1, "b": 2, "c": 3})
         dic = dict(r.hscan_iter("a"))
@@ -1990,6 +2164,7 @@ def test_hscan_iter_novalues(self, r):
         keys = list(r.hscan_iter("a_notset", no_values=True))
         assert keys == []
 
+    @skip_if_server_version_lt("2.8.0")
     def test_zscan(self, r):
         r.zadd("a", {"a": 1, "b": 2, "c": 3})
         cursor, pairs = r.zscan("a")
@@ -1998,6 +2173,7 @@ def test_zscan(self, r):
         _, pairs = r.zscan("a", match="a")
         assert set(pairs) == {(b"a", 1)}
 
+    @skip_if_server_version_lt("2.8.0")
     def test_zscan_iter(self, r):
         r.zadd("a", {"a": 1, "b": 2, "c": 3})
         pairs = list(r.zscan_iter("a"))
@@ -2039,6 +2215,7 @@ def test_sinter(self, r):
         assert r.sinter("a", "b") == {b"2", b"3"}
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_sintercard(self, r):
         r.sadd("a", 1, 2, 3)
         r.sadd("b", 1, 2, 3)
@@ -2066,6 +2243,7 @@ def test_smembers(self, r):
         r.sadd("a", "1", "2", "3")
         assert r.smembers("a") == {b"1", b"2", b"3"}
 
+    @skip_if_server_version_lt("6.2.0")
     def test_smismember(self, r):
         r.sadd("a", "1", "2", "3")
         result_list = [True, False, True, True]
@@ -2087,6 +2265,7 @@ def test_spop(self, r):
         assert value in s
         assert r.smembers("a") == set(s) - {value}
 
+    @skip_if_server_version_lt("3.2.0")
     def test_spop_multi_value(self, r):
         s = [b"1", b"2", b"3"]
         r.sadd("a", *s)
@@ -2107,6 +2286,7 @@ def test_srandmember(self, r):
         r.sadd("a", *s)
         assert r.srandmember("a") in s
 
+    @skip_if_server_version_lt("2.6.0")
     def test_srandmember_multi_value(self, r):
         s = [b"1", b"2", b"3"]
         r.sadd("a", *s)
@@ -2133,11 +2313,13 @@ def test_sunionstore(self, r):
         assert r.sunionstore("c", "a", "b") == 3
         assert r.smembers("c") == {b"1", b"2", b"3"}
 
+    @skip_if_server_version_lt("1.0.0")
     def test_debug_segfault(self, r):
         with pytest.raises(NotImplementedError):
             r.debug_segfault()
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.2.0")
     def test_script_debug(self, r):
         with pytest.raises(NotImplementedError):
             r.script_debug()
@@ -2205,6 +2387,7 @@ def test_zadd_incr_with_xx(self, r):
         # valkey-py
         assert r.zadd("a", {"a1": 1}, xx=True, incr=True) is None
 
+    @skip_if_server_version_lt("6.2.0")
     def test_zadd_gt_lt(self, r):
         r.zadd("a", {"a": 2})
         assert r.zadd("a", {"a": 5}, gt=True, ch=True) == 1
@@ -2235,6 +2418,7 @@ def test_zcount(self, r):
         assert r.zcount("a", 10, 20) == 0
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_zdiff(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         r.zadd("b", {"a1": 1, "a2": 2})
@@ -2247,6 +2431,7 @@ def test_zdiff(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_zdiffstore(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         r.zadd("b", {"a1": 1, "a2": 2})
@@ -2266,12 +2451,14 @@ def test_zincrby(self, r):
         assert r.zscore("a", "a2") == 3.0
         assert r.zscore("a", "a3") == 8.0
 
+    @skip_if_server_version_lt("2.8.9")
     def test_zlexcount(self, r):
         r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert r.zlexcount("a", "-", "+") == 7
         assert r.zlexcount("a", "[b", "[f") == 5
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_zinter(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 1})
         r.zadd("b", {"a1": 2, "a2": 2, "a3": 2})
@@ -2310,6 +2497,7 @@ def test_zinter(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_zintercard(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 1})
         r.zadd("b", {"a1": 2, "a2": 2, "a3": 2})
@@ -2369,6 +2557,7 @@ def test_zinterstore_with_weight(self, r):
             [[b"a3", 20], [b"a1", 23]],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     def test_zpopmax(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         assert_resp_response(r, r.zpopmax("a"), [(b"a3", 3)], [b"a3", 3.0])
@@ -2380,6 +2569,7 @@ def test_zpopmax(self, r):
             [[b"a2", 2], [b"a1", 1]],
         )
 
+    @skip_if_server_version_lt("4.9.0")
     def test_zpopmin(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         assert_resp_response(r, r.zpopmin("a"), [(b"a1", 1)], [b"a1", 1.0])
@@ -2391,6 +2581,7 @@ def test_zpopmin(self, r):
             [[b"a2", 2], [b"a3", 3]],
         )
 
+    @skip_if_server_version_lt("6.2.0")
     def test_zrandemember(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
         assert r.zrandmember("a") is not None
@@ -2408,6 +2599,7 @@ def test_zrandemember(self, r):
         assert len(r.zrandmember("a", -10)) == 10
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("4.9.0")
     def test_bzpopmax(self, r):
         r.zadd("a", {"a1": 1, "a2": 2})
         r.zadd("b", {"b1": 10, "b2": 20})
@@ -2430,6 +2622,7 @@ def test_bzpopmax(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("4.9.0")
     def test_bzpopmin(self, r):
         r.zadd("a", {"a1": 1, "a2": 2})
         r.zadd("b", {"b1": 10, "b2": 20})
@@ -2452,6 +2645,7 @@ def test_bzpopmin(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_zmpop(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         assert_resp_response(
@@ -2471,6 +2665,7 @@ def test_zmpop(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_bzmpop(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         assert_resp_response(
@@ -2527,6 +2722,7 @@ def test_zrange_errors(self, r):
         with pytest.raises(exceptions.DataError):
             r.zrange("a", 0, 1, byscore=True, withscores=True, num=2)
 
+    @skip_if_server_version_lt("6.2.0")
     def test_zrange_params(self, r):
         # bylex
         r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
@@ -2565,6 +2761,7 @@ def test_zrange_params(self, r):
         assert r.zrange("a", 0, 1, desc=True) == [b"a5", b"a4"]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_zrangestore(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3})
         assert r.zrangestore("b", "a", 0, 1)
@@ -2587,6 +2784,7 @@ def test_zrangestore(self, r):
         assert r.zrangestore("b", "a", "[a2", "(a3", bylex=True, offset=0, num=1)
         assert r.zrange("b", 0, -1) == [b"a2"]
 
+    @skip_if_server_version_lt("2.8.9")
     def test_zrangebylex(self, r):
         r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert r.zrangebylex("a", "-", "[c") == [b"a", b"b", b"c"]
@@ -2595,6 +2793,7 @@ def test_zrangebylex(self, r):
         assert r.zrangebylex("a", "[f", "+") == [b"f", b"g"]
         assert r.zrangebylex("a", "-", "+", start=3, num=2) == [b"d", b"e"]
 
+    @skip_if_server_version_lt("2.9.9")
     def test_zrevrangebylex(self, r):
         r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert r.zrevrangebylex("a", "[c", "-") == [b"c", b"b", b"a"]
@@ -2628,6 +2827,7 @@ def test_zrank(self, r):
         assert r.zrank("a", "a2") == 1
         assert r.zrank("a", "a6") is None
 
+    @skip_if_server_version_lt("7.2.0")
     def test_zrank_withscore(self, r: valkey.Valkey):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
         assert r.zrank("a", "a1") == 0
@@ -2648,6 +2848,7 @@ def test_zrem_multiple_keys(self, r):
         assert r.zrem("a", "a1", "a2") == 2
         assert r.zrange("a", 0, 5) == [b"a3"]
 
+    @skip_if_server_version_lt("2.8.9")
     def test_zremrangebylex(self, r):
         r.zadd("a", {"a": 0, "b": 0, "c": 0, "d": 0, "e": 0, "f": 0, "g": 0})
         assert r.zremrangebylex("a", "-", "[c") == 3
@@ -2721,6 +2922,7 @@ def test_zrevrank(self, r):
         assert r.zrevrank("a", "a2") == 3
         assert r.zrevrank("a", "a6") is None
 
+    @skip_if_server_version_lt("7.2.0")
     def test_zrevrank_withscore(self, r):
         r.zadd("a", {"a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5})
         assert r.zrevrank("a", "a1") == 4
@@ -2738,6 +2940,7 @@ def test_zscore(self, r):
         assert r.zscore("a", "a4") is None
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_zunion(self, r):
         r.zadd("a", {"a1": 1, "a2": 1, "a3": 1})
         r.zadd("b", {"a1": 2, "a2": 2, "a3": 2})
@@ -2824,6 +3027,7 @@ def test_zunionstore_with_weight(self, r):
             [[b"a2", 5], [b"a4", 12], [b"a3", 20], [b"a1", 23]],
         )
 
+    @skip_if_server_version_lt("6.1.240")
     def test_zmscore(self, r):
         with pytest.raises(exceptions.DataError):
             r.zmscore("invalid_key", [])
@@ -2834,6 +3038,7 @@ def test_zmscore(self, r):
         assert r.zmscore("a", ["a1", "a2", "a3", "a4"]) == [1.0, 2.0, 3.5, None]
 
     # HYPERLOGLOG TESTS
+    @skip_if_server_version_lt("2.8.9")
     def test_pfadd(self, r):
         members = {b"1", b"2", b"3"}
         assert r.pfadd("a", *members) == 1
@@ -2841,6 +3046,7 @@ def test_pfadd(self, r):
         assert r.pfcount("a") == len(members)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.9")
     def test_pfcount(self, r):
         members = {b"1", b"2", b"3"}
         r.pfadd("a", *members)
@@ -2851,6 +3057,7 @@ def test_pfcount(self, r):
         assert r.pfcount("a", "b") == len(members_b.union(members))
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.9")
     def test_pfmerge(self, r):
         mema = {b"1", b"2", b"3"}
         memb = {b"2", b"3", b"4"}
@@ -2933,6 +3140,7 @@ def test_hincrby(self, r):
         assert r.hincrby("a", "1", amount=2) == 3
         assert r.hincrby("a", "1", amount=-2) == 1
 
+    @skip_if_server_version_lt("2.6.0")
     def test_hincrbyfloat(self, r):
         assert r.hincrbyfloat("a", "1") == 1.0
         assert r.hincrbyfloat("a", "1") == 2.0
@@ -2978,6 +3186,7 @@ def test_hvals(self, r):
         remote_vals = r.hvals("a")
         assert sorted(local_vals) == sorted(remote_vals)
 
+    @skip_if_server_version_lt("3.2.0")
     def test_hstrlen(self, r):
         r.hset("a", mapping={"1": "22", "2": "333"})
         assert r.hstrlen("a", "1") == 2
@@ -3124,6 +3333,7 @@ def test_sort_all_options(self, r):
         assert num == 4
         assert r.lrange("sorted", 0, 10) == [b"vodka", b"milk", b"gin", b"apple juice"]
 
+    @skip_if_server_version_lt("7.0.0")
     @pytest.mark.onlynoncluster
     def test_sort_ro(self, r):
         r["score:1"] = 8
@@ -3202,15 +3412,24 @@ def test_cluster_slaves(self, mock_cluster_resp_slaves):
         assert isinstance(mock_cluster_resp_slaves.cluster("slaves", "nodeid"), dict)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.0.0")
+    @skip_if_server_version_gte("7.0.0")
+    def test_readwrite(self, r):
+        assert r.readwrite()
+
+    @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.0.0")
     def test_readonly_invalid_cluster_state(self, r):
         with pytest.raises(exceptions.ValkeyError):
             r.readonly()
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.0.0")
     def test_readonly(self, mock_cluster_resp_ok):
         assert mock_cluster_resp_ok.readonly() is True
 
     # GEO COMMANDS
+    @skip_if_server_version_lt("3.2.0")
     def test_geoadd(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3220,6 +3439,7 @@ def test_geoadd(self, r):
         assert r.geoadd("barcelona", values) == 2
         assert r.zcard("barcelona") == 2
 
+    @skip_if_server_version_lt("6.2.0")
     def test_geoadd_nx(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3235,6 +3455,7 @@ def test_geoadd_nx(self, r):
         assert r.geoadd("a", values, nx=True) == 1
         assert r.zrange("a", 0, -1) == [b"place3", b"place2", b"place1"]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_geoadd_xx(self, r):
         values = (2.1909389952632, 41.433791470673, "place1")
         assert r.geoadd("a", values) == 1
@@ -3246,6 +3467,7 @@ def test_geoadd_xx(self, r):
         assert r.geoadd("a", values, xx=True) == 0
         assert r.zrange("a", 0, -1) == [b"place1"]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_geoadd_ch(self, r):
         values = (2.1909389952632, 41.433791470673, "place1")
         assert r.geoadd("a", values) == 1
@@ -3257,10 +3479,12 @@ def test_geoadd_ch(self, r):
         assert r.geoadd("a", values, ch=True) == 2
         assert r.zrange("a", 0, -1) == [b"place1", b"place2"]
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geoadd_invalid_params(self, r):
         with pytest.raises(exceptions.ValkeyError):
             r.geoadd("barcelona", (1, 2))
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geodist(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3270,6 +3494,7 @@ def test_geodist(self, r):
         assert r.geoadd("barcelona", values) == 2
         assert r.geodist("barcelona", "place1", "place2") == 3067.4157
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geodist_units(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3279,15 +3504,18 @@ def test_geodist_units(self, r):
         r.geoadd("barcelona", values)
         assert r.geodist("barcelona", "place1", "place2", "km") == 3.0674
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geodist_missing_one_member(self, r):
         values = (2.1909389952632, 41.433791470673, "place1")
         r.geoadd("barcelona", values)
         assert r.geodist("barcelona", "place1", "missing_member", "km") is None
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geodist_invalid_units(self, r):
         with pytest.raises(exceptions.ValkeyError):
             assert r.geodist("x", "y", "z", "inches")
 
+    @skip_if_server_version_lt("3.2.0")
     def test_geohash(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3303,6 +3531,7 @@ def test_geohash(self, r):
         )
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     def test_geopos(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3324,9 +3553,16 @@ def test_geopos(self, r):
             ],
         )
 
+    @skip_if_server_version_lt("4.0.0")
     def test_geopos_no_value(self, r):
         assert r.geopos("barcelona", "place1", "place2") == [None, None]
 
+    @skip_if_server_version_lt("3.2.0")
+    @skip_if_server_version_gte("4.0.0")
+    def test_old_geopos_no_value(self, r):
+        assert r.geopos("barcelona", "place1", "place2") == []
+
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearch(self, r):
         values = (
             (2.1909389952632, 41.433791470673, "place1")
@@ -3357,6 +3593,7 @@ def test_geosearch(self, r):
         )[0] in [b"place1", b"place3", b"\x80place2"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearch_member(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3393,6 +3630,7 @@ def test_geosearch_member(self, r):
             ],
         ]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearch_sort(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3408,6 +3646,7 @@ def test_geosearch_sort(self, r):
         ) == [b"place2", b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearch_with(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3470,6 +3709,7 @@ def test_geosearch_with(self, r):
             == []
         )
 
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearch_negative(self, r):
         # not specifying member nor longitude and latitude
         with pytest.raises(exceptions.DataError):
@@ -3512,6 +3752,7 @@ def test_geosearch_negative(self, r):
             assert r.geosearch("barcelona", member="place3", radius=100, any=1)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearchstore(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3531,6 +3772,7 @@ def test_geosearchstore(self, r):
 
     @pytest.mark.onlynoncluster
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("6.2.0")
     def test_geosearchstore_dist(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3550,11 +3792,13 @@ def test_geosearchstore_dist(self, r):
         # instead of save the geo score, the distance is saved.
         assert r.zscore("places_barcelona", "place1") == 88.05060698409301
 
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_Issue2609(self, r):
         # test for issue #2609 (Geo search functions don't work with execute_command)
         r.geoadd(name="my-key", values=[1, 2, "data"])
         assert r.execute_command("GEORADIUS", "my-key", 1, 2, 400, "m") == [b"data"]
 
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3566,6 +3810,7 @@ def test_georadius(self, r):
         assert r.georadius("barcelona", 2.191, 41.433, 1000) == [b"place1"]
         assert r.georadius("barcelona", 2.187, 41.406, 1000) == [b"\x80place2"]
 
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_no_values(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3576,6 +3821,7 @@ def test_georadius_no_values(self, r):
         r.geoadd("barcelona", values)
         assert r.georadius("barcelona", 1, 2, 1000) == []
 
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_units(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3587,6 +3833,7 @@ def test_georadius_units(self, r):
         assert r.georadius("barcelona", 2.191, 41.433, 1, unit="km") == [b"place1"]
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_with(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3641,6 +3888,7 @@ def test_georadius_with(self, r):
             == []
         )
 
+    @skip_if_server_version_lt("6.2.0")
     def test_georadius_count(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3654,6 +3902,7 @@ def test_georadius_count(self, r):
             b"place2"
         ]
 
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_sort(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3672,6 +3921,7 @@ def test_georadius_sort(self, r):
         ]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_store(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3685,6 +3935,7 @@ def test_georadius_store(self, r):
 
     @pytest.mark.onlynoncluster
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     def test_georadius_store_dist(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3698,6 +3949,7 @@ def test_georadius_store_dist(self, r):
         assert r.zscore("places_barcelona", "place1") == 88.05060698409301
 
     @skip_unless_arch_bits(64)
+    @skip_if_server_version_lt("3.2.0")
     def test_georadiusmember(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3729,6 +3981,7 @@ def test_georadiusmember(self, r):
             ],
         ]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_georadiusmember_count(self, r):
         values = (2.1909389952632, 41.433791470673, "place1") + (
             2.1873744593677,
@@ -3740,6 +3993,7 @@ def test_georadiusmember_count(self, r):
             b"\x80place2"
         ]
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xack(self, r):
         stream = "stream"
         group = "group"
@@ -3760,6 +4014,7 @@ def test_xack(self, r):
         assert r.xack(stream, group, m1) == 1
         assert r.xack(stream, group, m2, m3) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xadd(self, r):
         stream = "stream"
         message_id = r.xadd(stream, {"foo": "bar"})
@@ -3773,6 +4028,7 @@ def test_xadd(self, r):
         r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False)
         assert r.xlen(stream) == 2
 
+    @skip_if_server_version_lt("6.2.0")
     def test_xadd_nomkstream(self, r):
         # nomkstream option
         stream = "stream"
@@ -3782,6 +4038,7 @@ def test_xadd_nomkstream(self, r):
         r.xadd(stream, {"some": "other"}, nomkstream=True)
         assert r.xlen(stream) == 3
 
+    @skip_if_server_version_lt("6.2.0")
     def test_xadd_minlen_and_limit(self, r):
         stream = "stream"
 
@@ -3827,12 +4084,14 @@ def test_xadd_minlen_and_limit(self, r):
         r.xadd(stream, {"foo": "bar"})
         assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m3)
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xadd_explicit_ms(self, r: valkey.Valkey):
         stream = "stream"
         message_id = r.xadd(stream, {"foo": "bar"}, "9999999999999999999-*")
         ms = message_id[: message_id.index(b"-")]
         assert ms == b"9999999999999999999"
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xautoclaim(self, r):
         stream = "stream"
         group = "group"
@@ -3865,6 +4124,7 @@ def test_xautoclaim(self, r):
             stream, group, consumer1, min_idle_time=0, start_id=message_id2, justid=True
         ) == [message_id2]
 
+    @skip_if_server_version_lt("6.2.0")
     def test_xautoclaim_negative(self, r):
         stream = "stream"
         group = "group"
@@ -3876,6 +4136,7 @@ def test_xautoclaim_negative(self, r):
         with pytest.raises(valkey.DataError):
             r.xautoclaim(stream, group, consumer, min_idle_time=0, count=-1)
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xclaim(self, r):
         stream = "stream"
         group = "group"
@@ -3912,6 +4173,7 @@ def test_xclaim(self, r):
             justid=True,
         ) == [message_id]
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xclaim_trimmed(self, r):
         # xclaim should not raise an exception if the item is not there
         stream = "stream"
@@ -3935,6 +4197,7 @@ def test_xclaim_trimmed(self, r):
         assert len(item) == 1
         assert item[0][0] == sid2
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xdel(self, r):
         stream = "stream"
 
@@ -3949,6 +4212,7 @@ def test_xdel(self, r):
         assert r.xdel(stream, m1) == 1
         assert r.xdel(stream, m2, m3) == 2
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xgroup_create(self, r):
         # tests xgroup_create and xinfo_groups
         stream = "stream"
@@ -3971,6 +4235,7 @@ def test_xgroup_create(self, r):
         ]
         assert r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xgroup_create_mkstream(self, r):
         # tests xgroup_create and xinfo_groups
         stream = "stream"
@@ -3996,6 +4261,7 @@ def test_xgroup_create_mkstream(self, r):
         ]
         assert r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xgroup_create_entriesread(self, r: valkey.Valkey):
         stream = "stream"
         group = "group"
@@ -4017,6 +4283,7 @@ def test_xgroup_create_entriesread(self, r: valkey.Valkey):
         ]
         assert r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xgroup_delconsumer(self, r):
         stream = "stream"
         group = "group"
@@ -4034,6 +4301,7 @@ def test_xgroup_delconsumer(self, r):
         # deleting the consumer should return 2 pending messages
         assert r.xgroup_delconsumer(stream, group, consumer) == 2
 
+    @skip_if_server_version_lt("6.2.0")
     def test_xgroup_createconsumer(self, r):
         stream = "stream"
         group = "group"
@@ -4049,6 +4317,7 @@ def test_xgroup_createconsumer(self, r):
         # deleting the consumer should return 2 pending messages
         assert r.xgroup_delconsumer(stream, group, consumer) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xgroup_destroy(self, r):
         stream = "stream"
         group = "group"
@@ -4060,6 +4329,7 @@ def test_xgroup_destroy(self, r):
         r.xgroup_create(stream, group, 0)
         assert r.xgroup_destroy(stream, group)
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xgroup_setid(self, r):
         stream = "stream"
         group = "group"
@@ -4080,6 +4350,7 @@ def test_xgroup_setid(self, r):
         ]
         assert r.xinfo_groups(stream) == expected
 
+    @skip_if_server_version_lt("7.2.0")
     def test_xinfo_consumers(self, r):
         stream = "stream"
         group = "group"
@@ -4106,6 +4377,7 @@ def test_xinfo_consumers(self, r):
         assert isinstance(info[1].pop("inactive"), int)
         assert info == expected
 
+    @skip_if_server_version_lt("7.0.0")
     def test_xinfo_stream(self, r):
         stream = "stream"
         m1 = r.xadd(stream, {"foo": "bar"})
@@ -4119,6 +4391,7 @@ def test_xinfo_stream(self, r):
         assert info["entries-added"] == 2
         assert info["recorded-first-entry-id"] == m1
 
+    @skip_if_server_version_lt("6.0.0")
     def test_xinfo_stream_full(self, r):
         stream = "stream"
         group = "group"
@@ -4135,6 +4408,7 @@ def test_xinfo_stream_full(self, r):
         )
         assert len(info["groups"]) == 1
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xlen(self, r):
         stream = "stream"
         assert r.xlen(stream) == 0
@@ -4142,6 +4416,7 @@ def test_xlen(self, r):
         r.xadd(stream, {"foo": "bar"})
         assert r.xlen(stream) == 2
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xpending(self, r):
         stream = "stream"
         group = "group"
@@ -4170,6 +4445,7 @@ def test_xpending(self, r):
         }
         assert r.xpending(stream, group) == expected
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xpending_range(self, r):
         stream = "stream"
         group = "group"
@@ -4200,6 +4476,7 @@ def test_xpending_range(self, r):
         assert response[0]["message_id"] == m1
         assert response[0]["consumer"] == consumer1.encode()
 
+    @skip_if_server_version_lt("6.2.0")
     def test_xpending_range_idle(self, r):
         stream = "stream"
         group = "group"
@@ -4240,6 +4517,7 @@ def test_xpending_range_negative(self, r):
                 stream, group, min=None, max=None, count=None, consumername=0
             )
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xrange(self, r):
         stream = "stream"
         m1 = r.xadd(stream, {"foo": "bar"})
@@ -4262,6 +4540,7 @@ def get_ids(results):
         results = r.xrange(stream, max=m2, count=1)
         assert get_ids(results) == [m1]
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xread(self, r):
         stream = "stream"
         m1 = r.xadd(stream, {"foo": "bar"})
@@ -4301,6 +4580,7 @@ def test_xread(self, r):
         # xread starting at the last message returns an empty list
         assert_resp_response(r, r.xread(streams={stream: m2}), [], {})
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xreadgroup(self, r):
         stream = "stream"
         group = "group"
@@ -4374,6 +4654,7 @@ def test_xreadgroup(self, r):
             {stream_name: [expected_entries]},
         )
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xrevrange(self, r):
         stream = "stream"
         m1 = r.xadd(stream, {"foo": "bar"})
@@ -4396,6 +4677,7 @@ def get_ids(results):
         results = r.xrevrange(stream, min=m2, count=1)
         assert get_ids(results) == [m4]
 
+    @skip_if_server_version_lt("5.0.0")
     def test_xtrim(self, r):
         stream = "stream"
 
@@ -4414,6 +4696,7 @@ def test_xtrim(self, r):
         # 1 message is trimmed
         assert r.xtrim(stream, 3, approximate=False) == 1
 
+    @skip_if_server_version_lt("6.2.4")
     def test_xtrim_minlen_and_length_args(self, r):
         stream = "stream"
 
@@ -4522,6 +4805,7 @@ def test_bitfield_operations(self, r):
         )
         assert resp == [0, None, 255]
 
+    @skip_if_server_version_lt("6.0.0")
     def test_bitfield_ro(self, r: valkey.Valkey):
         bf = r.bitfield("a")
         resp = bf.set("u8", 8, 255).execute()
@@ -4534,17 +4818,21 @@ def test_bitfield_ro(self, r: valkey.Valkey):
         resp = r.bitfield_ro("a", "u8", 0, items)
         assert resp == [0, 15, 15, 14]
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_help(self, r):
         with pytest.raises(NotImplementedError):
             r.memory_help()
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_doctor(self, r):
         with pytest.raises(NotImplementedError):
             r.memory_doctor()
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_malloc_stats(self, r):
         assert r.memory_malloc_stats()
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_stats(self, r):
         # put a key into the current db to make sure that "db.<current-db>"
         # has data
@@ -4556,10 +4844,12 @@ def test_memory_stats(self, r):
             if key.startswith("db."):
                 assert isinstance(value, dict)
 
+    @skip_if_server_version_lt("4.0.0")
     def test_memory_usage(self, r):
         r.set("foo", "bar")
         assert isinstance(r.memory_usage("foo"), int)
 
+    @skip_if_server_version_lt("7.0.0")
     def test_latency_histogram_not_implemented(self, r: valkey.Valkey):
         with pytest.raises(NotImplementedError):
             r.latency_histogram()
@@ -4581,20 +4871,24 @@ def test_latency_latest(self, r: valkey.Valkey):
     def test_latency_reset(self, r: valkey.Valkey):
         assert r.latency_reset() == 0
 
+    @skip_if_server_version_lt("4.0.0")
     def test_module_list(self, r):
         assert isinstance(r.module_list(), list)
         for x in r.module_list():
             assert isinstance(x, dict)
 
+    @skip_if_server_version_lt("2.8.13")
     def test_command_count(self, r):
         res = r.command_count()
         assert isinstance(res, int)
         assert res >= 100
 
+    @skip_if_server_version_lt("7.0.0")
     def test_command_docs(self, r):
         with pytest.raises(NotImplementedError):
             r.command_docs("set")
 
+    @skip_if_server_version_lt("7.0.0")
     def test_command_list(self, r: valkey.Valkey):
         assert len(r.command_list()) > 300
         assert len(r.command_list(module="fakemod")) == 0
@@ -4604,6 +4898,7 @@ def test_command_list(self, r: valkey.Valkey):
             r.command_list(category="list", pattern="l*")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.13")
     def test_command_getkeys(self, r):
         res = r.command_getkeys("MSET", "a", "b", "c", "d", "e", "f")
         assert_resp_response(r, res, ["a", "c", "e"], [b"a", b"c", b"e"])
@@ -4623,6 +4918,7 @@ def test_command_getkeys(self, r):
             r, res, ["key1", "key2", "key3"], [b"key1", b"key2", b"key3"]
         )
 
+    @skip_if_server_version_lt("2.8.13")
     def test_command(self, r):
         res = r.command()
         assert len(res) >= 100
@@ -4631,6 +4927,7 @@ def test_command(self, r):
         assert "get" in cmds
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_command_getkeysandflags(self, r: valkey.Valkey):
         assert_resp_response(
             r,
@@ -4646,6 +4943,7 @@ def test_command_getkeysandflags(self, r: valkey.Valkey):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("4.0.0")
     def test_module(self, r):
         with pytest.raises(valkey.exceptions.ModuleError) as excinfo:
             r.module_load("/some/fake/path")
@@ -4656,6 +4954,7 @@ def test_module(self, r):
             assert "Error loading the extension." in str(excinfo.value)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_module_loadex(self, r: valkey.Valkey):
         with pytest.raises(valkey.exceptions.ModuleError) as excinfo:
             r.module_loadex("/some/fake/path")
@@ -4665,6 +4964,7 @@ def test_module_loadex(self, r: valkey.Valkey):
             r.module_loadex("/some/fake/path", ["name", "value"], ["arg1", "arg2"])
             assert "Error loading the extension." in str(excinfo.value)
 
+    @skip_if_server_version_lt("2.6.0")
     def test_restore(self, r):
         # standard restore
         key = "foo"
@@ -4689,6 +4989,7 @@ def test_restore(self, r):
         assert r.restore(key2, 0, dumpdata)
         assert r.ttl(key2) == -1
 
+    @skip_if_server_version_lt("5.0.0")
     def test_restore_idletime(self, r):
         key = "yayakey"
         r.set(key, "blee!")
@@ -4697,6 +4998,7 @@ def test_restore_idletime(self, r):
         assert r.restore(key, 0, dumpdata, idletime=5)
         assert r.get(key) == b"blee!"
 
+    @skip_if_server_version_lt("5.0.0")
     def test_restore_frequency(self, r):
         key = "yayakey"
         r.set(key, "blee!")
@@ -4706,6 +5008,7 @@ def test_restore_frequency(self, r):
         assert r.get(key) == b"blee!"
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("5.0.0")
     def test_replicaof(self, r):
         with pytest.raises(valkey.ResponseError):
             assert r.replicaof("NO ONE")
@@ -4716,6 +5019,7 @@ def test_shutdown(self, r: valkey.Valkey):
         r.execute_command("SHUTDOWN", "NOSAVE")
         r.execute_command.assert_called_once_with("SHUTDOWN", "NOSAVE")
 
+    @skip_if_server_version_lt("7.0.0")
     def test_shutdown_with_params(self, r: valkey.Valkey):
         r.execute_command = mock.MagicMock()
         r.execute_command("SHUTDOWN", "SAVE", "NOW", "FORCE")
@@ -4725,6 +5029,7 @@ def test_shutdown_with_params(self, r: valkey.Valkey):
 
     @pytest.mark.replica
     @pytest.mark.xfail(strict=False)
+    @skip_if_server_version_lt("2.8.0")
     def test_sync(self, r):
         r.flushdb()
         time.sleep(1)
@@ -4733,6 +5038,7 @@ def test_sync(self, r):
         assert b"VALKEY" in res
 
     @pytest.mark.replica
+    @skip_if_server_version_lt("2.8.0")
     def test_psync(self, r):
         r2 = valkey.Valkey(port=6380, decode_responses=False)
         res = r2.psync(r2.client_id(), 1)
diff --git a/tests/test_connection.py b/tests/test_connection.py
index b4fe7107..9c60aa8f 100644
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -18,6 +18,7 @@
 from valkey.retry import Retry
 from valkey.utils import HIREDIS_AVAILABLE
 
+from .conftest import skip_if_server_version_lt
 from .mocks import MockSocket
 
 
@@ -32,6 +33,7 @@ def test_invalid_response(r):
     assert str(cm.value) == f"Protocol Error: {raw!r}"
 
 
+@skip_if_server_version_lt("4.0.0")
 @pytest.mark.valkeymod
 def test_loading_external_modules(r):
     def inner():
diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py
index ede90c15..07806fa7 100644
--- a/tests/test_connection_pool.py
+++ b/tests/test_connection_pool.py
@@ -10,7 +10,7 @@
 from valkey.connection import to_bool
 from valkey.utils import SSL_AVAILABLE
 
-from .conftest import _get_client
+from .conftest import _get_client, skip_if_server_version_lt
 from .test_pubsub import wait_for_message
 
 
@@ -213,11 +213,13 @@ def test_port(self):
         assert pool.connection_class == valkey.Connection
         assert pool.connection_kwargs == {"host": "localhost", "port": 6380}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username(self):
         pool = valkey.ConnectionPool.from_url("valkey://myuser:@localhost")
         assert pool.connection_class == valkey.Connection
         assert pool.connection_kwargs == {"host": "localhost", "username": "myuser"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_quoted_username(self):
         pool = valkey.ConnectionPool.from_url(
             "valkey://%2Fmyuser%2F%2B name%3D%24+:@localhost"
@@ -243,6 +245,7 @@ def test_quoted_password(self):
             "password": "/mypass/+ word=$+",
         }
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username_and_password(self):
         pool = valkey.ConnectionPool.from_url("valkey://myuser:mypass@localhost")
         assert pool.connection_class == valkey.Connection
@@ -377,11 +380,13 @@ def test_defaults(self):
         assert pool.connection_class == valkey.UnixDomainSocketConnection
         assert pool.connection_kwargs == {"path": "/socket"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_username(self):
         pool = valkey.ConnectionPool.from_url("unix://myuser:@/socket")
         assert pool.connection_class == valkey.UnixDomainSocketConnection
         assert pool.connection_kwargs == {"path": "/socket", "username": "myuser"}
 
+    @skip_if_server_version_lt("6.0.0")
     def test_quoted_username(self):
         pool = valkey.ConnectionPool.from_url(
             "unix://%2Fmyuser%2F%2B name%3D%24+:@/socket"
@@ -504,6 +509,7 @@ def test_on_connect_error(self):
         assert not pool._available_connections[0]._sock
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     def test_busy_loading_disconnects_socket(self, r):
         """
         If Valkey raises a LOADING error, the connection should be
@@ -514,6 +520,7 @@ def test_busy_loading_disconnects_socket(self, r):
         assert not r.connection._sock
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     def test_busy_loading_from_pipeline_immediate_command(self, r):
         """
         BusyLoadingErrors should raise from Pipelines that execute a
@@ -528,6 +535,7 @@ def test_busy_loading_from_pipeline_immediate_command(self, r):
         assert not pool._available_connections[0]._sock
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.8")
     def test_busy_loading_from_pipeline(self, r):
         """
         BusyLoadingErrors should be raised from a pipeline execution
@@ -542,6 +550,7 @@ def test_busy_loading_from_pipeline(self, r):
         assert len(pool._available_connections) == 1
         assert not pool._available_connections[0]._sock
 
+    @skip_if_server_version_lt("2.8.8")
     def test_read_only_error(self, r):
         "READONLY errors get turned into ReadOnlyError exceptions"
         with pytest.raises(valkey.ReadOnlyError):
diff --git a/tests/test_function.py b/tests/test_function.py
index e406287a..a4637872 100644
--- a/tests/test_function.py
+++ b/tests/test_function.py
@@ -1,7 +1,7 @@
 import pytest
 from valkey.exceptions import ResponseError
 
-from .conftest import assert_resp_response
+from .conftest import assert_resp_response, skip_if_server_version_lt
 
 engine = "lua"
 lib = "mylib"
@@ -15,6 +15,7 @@
                 redis.call('GET', keys[1]) end)"
 
 
+@skip_if_server_version_lt("7.0.0")
 class TestFunction:
     @pytest.fixture(autouse=True)
     def reset_functions(self, r):
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
index 4bfc08e0..065f898c 100644
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -4,7 +4,7 @@
 import pytest
 import valkey
 
-from .conftest import wait_for_command
+from .conftest import skip_if_server_version_lt, wait_for_command
 
 
 class TestPipeline:
@@ -388,6 +388,7 @@ def test_pipeline_with_bitfield(self, r):
             assert response == [True, [0, 0, 15, 15, 14], b"1"]
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.0.0")
     def test_pipeline_discard(self, r):
         # empty pipeline should raise an error
         with r.pipeline() as pipe:
diff --git a/tests/test_pubsub.py b/tests/test_pubsub.py
index 3d99e9e1..43b38d00 100644
--- a/tests/test_pubsub.py
+++ b/tests/test_pubsub.py
@@ -12,7 +12,7 @@
 from valkey.exceptions import ConnectionError
 from valkey.utils import HIREDIS_AVAILABLE
 
-from .conftest import _get_client, is_resp2_connection
+from .conftest import _get_client, is_resp2_connection, skip_if_server_version_lt
 
 
 def wait_for_message(
@@ -107,11 +107,13 @@ def test_pattern_subscribe_unsubscribe(self, r):
         self._test_subscribe_unsubscribe(**kwargs)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_subscribe_unsubscribe(self, r):
         kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel")
         self._test_subscribe_unsubscribe(**kwargs)
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_subscribe_unsubscribe_cluster(self, r):
         node_channels = defaultdict(int)
         p = r.pubsub()
@@ -185,6 +187,7 @@ def test_resubscribe_to_patterns_on_reconnection(self, r):
         self._test_resubscribe_on_reconnection(**kwargs)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_resubscribe_to_shard_channels_on_reconnection(self, r):
         kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel")
         self._test_resubscribe_on_reconnection(**kwargs)
@@ -245,11 +248,13 @@ def test_subscribe_property_with_patterns(self, r):
         self._test_subscribed_property(**kwargs)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_subscribe_property_with_shard_channels(self, r):
         kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel")
         self._test_subscribed_property(**kwargs)
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_subscribe_property_with_shard_channels_cluster(self, r):
         p = r.pubsub()
         keys = ["foo", "bar", "uni" + chr(4456) + "code"]
@@ -309,6 +314,7 @@ def test_subscribe_property_with_shard_channels_cluster(self, r):
         # now we're finally unsubscribed
         assert p.subscribed is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_ignore_all_subscribe_messages(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
 
@@ -328,6 +334,7 @@ def test_ignore_all_subscribe_messages(self, r):
             assert wait_for_message(p, func=get_func) is None
         assert p.subscribed is False
 
+    @skip_if_server_version_lt("7.0.0")
     def test_ignore_individual_subscribe_messages(self, r):
         p = r.pubsub()
 
@@ -358,6 +365,7 @@ def test_sub_unsub_resub_patterns(self, r):
         self._test_sub_unsub_resub(**kwargs)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_sub_unsub_resub_shard_channels(self, r):
         kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel")
         self._test_sub_unsub_resub(**kwargs)
@@ -377,6 +385,7 @@ def _test_sub_unsub_resub(
         assert p.subscribed is True
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_sub_unsub_resub_shard_channels_cluster(self, r):
         p = r.pubsub()
         key = "foo"
@@ -404,6 +413,7 @@ def test_sub_unsub_all_resub_patterns(self, r):
         self._test_sub_unsub_all_resub(**kwargs)
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_sub_unsub_all_resub_shard_channels(self, r):
         kwargs = make_subscribe_test_data(r.pubsub(), "shard_channel")
         self._test_sub_unsub_all_resub(**kwargs)
@@ -423,6 +433,7 @@ def _test_sub_unsub_all_resub(
         assert p.subscribed is True
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_sub_unsub_all_resub_shard_channels_cluster(self, r):
         p = r.pubsub()
         key = "foo"
@@ -460,6 +471,7 @@ def test_published_message_to_channel(self, r):
         assert message == make_message("message", "foo", "test message")
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_published_message_to_shard_channel(self, r):
         p = r.pubsub()
         p.ssubscribe("foo")
@@ -471,6 +483,7 @@ def test_published_message_to_shard_channel(self, r):
         assert message == make_message("smessage", "foo", "test message")
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_published_message_to_shard_channel_cluster(self, r):
         p = r.pubsub()
         p.ssubscribe("foo")
@@ -514,6 +527,7 @@ def test_channel_message_handler(self, r):
         assert wait_for_message(p) is None
         assert self.message == make_message("message", "foo", "test message")
 
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_message_handler(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
         p.ssubscribe(foo=self.message_handler)
@@ -543,6 +557,7 @@ def test_unicode_channel_message_handler(self, r):
         assert wait_for_message(p) is None
         assert self.message == make_message("message", channel, "test message")
 
+    @skip_if_server_version_lt("7.0.0")
     def test_unicode_shard_channel_message_handler(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
         channel = "uni" + chr(4456) + "code"
@@ -586,6 +601,7 @@ def test_push_handler(self, r):
         assert self.message == ["my handler", [b"message", b"foo", b"test message"]]
 
     @pytest.mark.skipif(HIREDIS_AVAILABLE, reason="PythonParser only")
+    @skip_if_server_version_lt("7.0.0")
     def test_push_handler_sharded_pubsub(self, r):
         if is_resp2_connection(r):
             return
@@ -634,6 +650,7 @@ def test_pattern_subscribe_unsubscribe(self, r):
         p.punsubscribe(self.pattern)
         assert wait_for_message(p) == self.make_message("punsubscribe", self.pattern, 0)
 
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_subscribe_unsubscribe(self, r):
         p = r.pubsub()
         p.ssubscribe(self.channel)
@@ -665,6 +682,7 @@ def test_pattern_publish(self, r):
             "pmessage", self.channel, self.data, pattern=self.pattern
         )
 
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_publish(self, r):
         p = r.pubsub()
         p.ssubscribe(self.channel)
@@ -714,6 +732,7 @@ def test_pattern_message_handler(self, r):
             "pmessage", self.channel, new_data, pattern=self.pattern
         )
 
+    @skip_if_server_version_lt("7.0.0")
     def test_shard_channel_message_handler(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
         p.ssubscribe(**{self.channel: self.message_handler})
@@ -757,6 +776,7 @@ def test_channel_subscribe(self, r):
 
 class TestPubSubSubcommands:
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     def test_pubsub_channels(self, r):
         p = r.pubsub()
         p.subscribe("foo", "bar", "baz", "quux")
@@ -766,6 +786,7 @@ def test_pubsub_channels(self, r):
         assert all([channel in r.pubsub_channels() for channel in expected])
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("7.0.0")
     def test_pubsub_shardchannels(self, r):
         p = r.pubsub()
         p.ssubscribe("foo", "bar", "baz", "quux")
@@ -775,6 +796,7 @@ def test_pubsub_shardchannels(self, r):
         assert all([channel in r.pubsub_shardchannels() for channel in expected])
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_pubsub_shardchannels_cluster(self, r):
         channels = {
             b"foo": r.get_node_from_key("foo"),
@@ -796,6 +818,7 @@ def test_pubsub_shardchannels_cluster(self, r):
         )
 
     @pytest.mark.onlynoncluster
+    @skip_if_server_version_lt("2.8.0")
     def test_pubsub_numsub(self, r):
         p1 = r.pubsub()
         p1.subscribe("foo", "bar", "baz")
@@ -812,6 +835,7 @@ def test_pubsub_numsub(self, r):
         channels = [(b"foo", 1), (b"bar", 2), (b"baz", 3)]
         assert r.pubsub_numsub("foo", "bar", "baz") == channels
 
+    @skip_if_server_version_lt("2.8.0")
     def test_pubsub_numpat(self, r):
         p = r.pubsub()
         p.psubscribe("*oo", "*ar", "b*z")
@@ -820,6 +844,7 @@ def test_pubsub_numpat(self, r):
         assert r.pubsub_numpat() == 3
 
     @pytest.mark.onlycluster
+    @skip_if_server_version_lt("7.0.0")
     def test_pubsub_shardnumsub(self, r):
         channels = {
             b"foo": r.get_node_from_key("foo"),
@@ -846,6 +871,7 @@ def test_pubsub_shardnumsub(self, r):
 
 
 class TestPubSubPings:
+    @skip_if_server_version_lt("3.0.0")
     def test_send_pubsub_ping(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
         p.subscribe("foo")
@@ -854,6 +880,7 @@ def test_send_pubsub_ping(self, r):
             type="pong", channel=None, data="", pattern=None
         )
 
+    @skip_if_server_version_lt("3.0.0")
     def test_send_pubsub_ping_message(self, r):
         p = r.pubsub(ignore_subscribe_messages=True)
         p.subscribe("foo")
@@ -865,6 +892,7 @@ def test_send_pubsub_ping_message(self, r):
 
 @pytest.mark.onlynoncluster
 class TestPubSubConnectionKilled:
+    @skip_if_server_version_lt("3.0.0")
     def test_connection_error_raised_when_connection_dies(self, r):
         p = r.pubsub()
         p.subscribe("foo")
diff --git a/tests/test_scripting.py b/tests/test_scripting.py
index 82a99a37..d697136a 100644
--- a/tests/test_scripting.py
+++ b/tests/test_scripting.py
@@ -1,5 +1,6 @@
 import pytest
 import valkey
+from tests.conftest import skip_if_server_version_lt
 from valkey import exceptions
 from valkey.commands.core import Script
 
@@ -63,6 +64,7 @@ def test_eval_multiply(self, r):
         # 2 * 3 == 6
         assert r.eval(multiply_script, 1, "a", 3) == 6
 
+    @skip_if_server_version_lt("7.0.0")
     def test_eval_ro(self, r):
         r.set("a", "b")
         assert r.eval_ro("return redis.call('GET', KEYS[1])", 1, "a") == b"b"
@@ -115,6 +117,7 @@ def test_eval_crossslot(self, r):
         with pytest.raises(exceptions.ValkeyClusterException):
             r.eval(script, 2, "A{foo}", "B{bar}")
 
+    @skip_if_server_version_lt("6.2.0")
     def test_script_flush_620(self, r):
         r.set("a", 2)
         r.script_load(multiply_script)
@@ -149,6 +152,7 @@ def test_evalsha(self, r):
         # 2 * 3 == 6
         assert r.evalsha(sha, 1, "a", 3) == 6
 
+    @skip_if_server_version_lt("7.0.0")
     def test_evalsha_ro(self, r):
         r.set("a", "b")
         get_sha = r.script_load("return redis.call('GET', KEYS[1])")