Repository: redis/redis-py
Pull request #3305 (2024-07-03 17:45:30)

Format connection errors in the same way everywhere
### Pull Request check-list

_Please make sure to review and check all of these items:_

- [ ] Do tests and lints pass with this change?
- [ ] Do the CI tests pass with this change (enable it first in your forked repo and wait for the github action build to finish)?
- [ ] Is the new or changed code fully tested?
- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
- [ ] Is there an example added to the examples folder (if applicable)?
- [ ] Was the change added to CHANGES file?

_NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open._

### Description of change

Connection errors are formatted in four places, sync and async, network socket and unix socket. Each place has some small differences compared to the others, while they could be, and should be, formatted in an uniform way. Factor out the logic in a helper method and call that method in all four places.

This is a continuation of #3211 



Patch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index f6186b82..a619275b 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -25,8 +25,8 @@ permissions:
 
 env:
   CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
-  REDIS_IMAGE: redis/redis-stack-server:7.4.0-rc1
-  REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc1
+  REDIS_IMAGE: redis:7.4-rc2
+  REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc2
 
 jobs:
   dependency-audit:
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
index b3dae2a2..ec1ce5a9 100644
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -27,6 +27,8 @@ from typing import (
 )
 from urllib.parse import ParseResult, parse_qs, unquote, urlparse
 
+from ..utils import format_error_message
+
 # the functionality is available in 3.11.x but has a major issue before
 # 3.11.3. See https://github.com/redis/redis-py/issues/2633
 if sys.version_info >= (3, 11, 3):
@@ -345,9 +347,8 @@ class AbstractConnection:
     def _host_error(self) -> str:
         pass
 
-    @abstractmethod
     def _error_message(self, exception: BaseException) -> str:
-        pass
+        return format_error_message(self._host_error(), exception)
 
     async def on_connect(self) -> None:
         """Initialize the connection, authenticate and select a database"""
@@ -799,27 +800,6 @@ class Connection(AbstractConnection):
     def _host_error(self) -> str:
         return f"{self.host}:{self.port}"
 
-    def _error_message(self, exception: BaseException) -> str:
-        # args for socket.error can either be (errno, "message")
-        # or just "message"
-
-        host_error = self._host_error()
-
-        if not exception.args:
-            # asyncio has a bug where on Connection reset by peer, the
-            # exception is not instanciated, so args is empty. This is the
-            # workaround.
-            # See: https://github.com/redis/redis-py/issues/2237
-            # See: https://github.com/python/cpython/issues/94061
-            return f"Error connecting to {host_error}. Connection reset by peer"
-        elif len(exception.args) == 1:
-            return f"Error connecting to {host_error}. {exception.args[0]}."
-        else:
-            return (
-                f"Error {exception.args[0]} connecting to {host_error}. "
-                f"{exception}."
-            )
-
 
 class SSLConnection(Connection):
     """Manages SSL connections to and from the Redis server(s).
@@ -971,20 +951,6 @@ class UnixDomainSocketConnection(AbstractConnection):
     def _host_error(self) -> str:
         return self.path
 
-    def _error_message(self, exception: BaseException) -> str:
-        # args for socket.error can either be (errno, "message")
-        # or just "message"
-        host_error = self._host_error()
-        if len(exception.args) == 1:
-            return (
-                f"Error connecting to unix socket: {host_error}. {exception.args[0]}."
-            )
-        else:
-            return (
-                f"Error {exception.args[0]} connecting to unix socket: "
-                f"{host_error}. {exception.args[1]}."
-            )
-
 
 FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")
 
diff --git a/redis/commands/core.py b/redis/commands/core.py
index a56b3d2c..26af9eb9 100644
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -5126,9 +5126,8 @@ class HashCommands(CommandsProtocol):
             lt: Set expiry only when the new expiry is less than the current one.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `0` if the specified NX | XX | GT | LT condition was not met.
                 - `1` if the expiration time was set or updated.
                 - `2` if the field was deleted because the specified expiration time is
@@ -5187,9 +5186,8 @@ class HashCommands(CommandsProtocol):
             lt: Set expiry only when the new expiry is less than the current one.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `0` if the specified NX | XX | GT | LT condition was not met.
                 - `1` if the expiration time was set or updated.
                 - `2` if the field was deleted because the specified expiration time is
@@ -5248,9 +5246,8 @@ class HashCommands(CommandsProtocol):
             lt: Set expiry only when the new expiry is less than the current one.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `0` if the specified NX | XX | GT | LT condition was not met.
                 - `1` if the expiration time was set or updated.
                 - `2` if the field was deleted because the specified expiration time is
@@ -5315,9 +5312,8 @@ class HashCommands(CommandsProtocol):
             lt: Set expiry only when the new expiry is less than the current one.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `0` if the specified NX | XX | GT | LT condition was not met.
                 - `1` if the expiration time was set or updated.
                 - `2` if the field was deleted because the specified expiration time is
@@ -5362,9 +5358,8 @@ class HashCommands(CommandsProtocol):
                     expiration time.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `-1` if the field exists but has no associated expiration time.
                 - `1` if the expiration time was successfully removed from the field.
         """
@@ -5382,9 +5377,8 @@ class HashCommands(CommandsProtocol):
                     time.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `-1` if the field exists but has no associated expire time.
                 - A positive integer representing the expiration Unix timestamp in
                   seconds, if the field has an associated expiration time.
@@ -5405,9 +5399,8 @@ class HashCommands(CommandsProtocol):
                     time.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `-1` if the field exists but has no associated expire time.
                 - A positive integer representing the expiration Unix timestamp in
                   milliseconds, if the field has an associated expiration time.
@@ -5428,9 +5421,8 @@ class HashCommands(CommandsProtocol):
             fields: A list of fields within the hash for which to get the TTL.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `-1` if the field exists but has no associated expire time.
                 - A positive integer representing the TTL in seconds if the field has
                   an associated expiration time.
@@ -5451,9 +5443,8 @@ class HashCommands(CommandsProtocol):
             fields: A list of fields within the hash for which to get the TTL.
 
         Returns:
-            If the key does not exist, returns an empty list. If the key exists, returns
-            a list which contains for each field in the request:
-                - `-2` if the field does not exist.
+            Returns a list which contains for each field in the request:
+                - `-2` if the field does not exist, or if the key does not exist.
                 - `-1` if the field exists but has no associated expire time.
                 - A positive integer representing the TTL in milliseconds if the field
                   has an associated expiration time.
diff --git a/redis/connection.py b/redis/connection.py
index 728c2212..6e3b3ab0 100644
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -39,6 +39,7 @@ from .utils import (
     HIREDIS_AVAILABLE,
     HIREDIS_PACK_AVAILABLE,
     SSL_AVAILABLE,
+    format_error_message,
     get_lib_version,
     str_if_bytes,
 )
@@ -338,9 +339,8 @@ class AbstractConnection:
     def _host_error(self):
         pass
 
-    @abstractmethod
     def _error_message(self, exception):
-        pass
+        return format_error_message(self._host_error(), exception)
 
     def on_connect(self):
         "Initialize the connection, authenticate and select a database"
@@ -733,27 +733,6 @@ class Connection(AbstractConnection):
     def _host_error(self):
         return f"{self.host}:{self.port}"
 
-    def _error_message(self, exception):
-        # args for socket.error can either be (errno, "message")
-        # or just "message"
-
-        host_error = self._host_error()
-
-        if len(exception.args) == 1:
-            try:
-                return f"Error connecting to {host_error}. \
-                        {exception.args[0]}."
-            except AttributeError:
-                return f"Connection Error: {exception.args[0]}"
-        else:
-            try:
-                return (
-                    f"Error {exception.args[0]} connecting to "
-                    f"{host_error}. {exception.args[1]}."
-                )
-            except AttributeError:
-                return f"Connection Error: {exception.args[0]}"
-
 
 class SSLConnection(Connection):
     """Manages SSL connections to and from the Redis server(s).
@@ -930,20 +909,6 @@ class UnixDomainSocketConnection(AbstractConnection):
     def _host_error(self):
         return self.path
 
-    def _error_message(self, exception):
-        # args for socket.error can either be (errno, "message")
-        # or just "message"
-        host_error = self._host_error()
-        if len(exception.args) == 1:
-            return (
-                f"Error connecting to unix socket: {host_error}. {exception.args[0]}."
-            )
-        else:
-            return (
-                f"Error {exception.args[0]} connecting to unix socket: "
-                f"{host_error}. {exception.args[1]}."
-            )
-
 
 FALSE_STRINGS = ("0", "F", "FALSE", "N", "NO")
 
diff --git a/redis/utils.py b/redis/utils.py
index ea2eac14..360ee54b 100644
--- a/redis/utils.py
+++ b/redis/utils.py
@@ -141,3 +141,15 @@ def get_lib_version():
     except metadata.PackageNotFoundError:
         libver = "99.99.99"
     return libver
+
+
+def format_error_message(host_error: str, exception: BaseException) -> str:
+    if not exception.args:
+        return f"Error connecting to {host_error}."
+    elif len(exception.args) == 1:
+        return f"Error {exception.args[0]} connecting to {host_error}."
+    else:
+        return (
+            f"Error {exception.args[0]} connecting to {host_error}. "
+            f"{exception.args[1]}."
+        )


Test patch:
diff --git a/tests/test_asyncio/test_connection.py b/tests/test_asyncio/test_connection.py
index 6255ae7d..8f79f7d9 100644
--- a/tests/test_asyncio/test_connection.py
+++ b/tests/test_asyncio/test_connection.py
@@ -12,7 +12,12 @@ from redis._parsers import (
     _AsyncRESPBase,
 )
 from redis.asyncio import ConnectionPool, Redis
-from redis.asyncio.connection import Connection, UnixDomainSocketConnection, parse_url
+from redis.asyncio.connection import (
+    Connection,
+    SSLConnection,
+    UnixDomainSocketConnection,
+    parse_url,
+)
 from redis.asyncio.retry import Retry
 from redis.backoff import NoBackoff
 from redis.exceptions import ConnectionError, InvalidResponse, TimeoutError
@@ -494,18 +499,50 @@ async def test_connection_garbage_collection(request):
 
 
 @pytest.mark.parametrize(
-    "error, expected_message",
+    "conn, error, expected_message",
     [
-        (OSError(), "Error connecting to localhost:6379. Connection reset by peer"),
-        (OSError(12), "Error connecting to localhost:6379. 12."),
+        (SSLConnection(), OSError(), "Error connecting to localhost:6379."),
+        (SSLConnection(), OSError(12), "Error 12 connecting to localhost:6379."),
         (
+            SSLConnection(),
             OSError(12, "Some Error"),
-            "Error 12 connecting to localhost:6379. [Errno 12] Some Error.",
+            "Error 12 connecting to localhost:6379. Some Error.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(),
+            "Error connecting to unix:///tmp/redis.sock.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(12),
+            "Error 12 connecting to unix:///tmp/redis.sock.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(12, "Some Error"),
+            "Error 12 connecting to unix:///tmp/redis.sock. Some Error.",
         ),
     ],
 )
-async def test_connect_error_message(error, expected_message):
+async def test_format_error_message(conn, error, expected_message):
     """Test that the _error_message function formats errors correctly"""
-    conn = Connection()
     error_message = conn._error_message(error)
     assert error_message == expected_message
+
+
+async def test_network_connection_failure():
+    with pytest.raises(ConnectionError) as e:
+        redis = Redis(host="127.0.0.1", port=9999)
+        await redis.set("a", "b")
+    assert str(e.value).startswith("Error 111 connecting to 127.0.0.1:9999. Connect")
+
+
+async def test_unix_socket_connection_failure():
+    with pytest.raises(ConnectionError) as e:
+        redis = Redis(unix_socket_path="unix:///tmp/a.sock")
+        await redis.set("a", "b")
+    assert (
+        str(e.value)
+        == "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
+    )
diff --git a/tests/test_asyncio/test_hash.py b/tests/test_asyncio/test_hash.py
index d4f18053..e31ea7ea 100644
--- a/tests/test_asyncio/test_hash.py
+++ b/tests/test_asyncio/test_hash.py
@@ -45,7 +45,7 @@ async def test_hexpire_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 async def test_hexpire_nonexistent_key_or_field(r):
     await r.delete("test:hash")
-    assert await r.hexpire("test:hash", 1, "field1") == []
+    assert await r.hexpire("test:hash", 1, "field1") == [-2]
     await r.hset("test:hash", "field1", "value1")
     assert await r.hexpire("test:hash", 1, "nonexistent_field") == [-2]
 
@@ -105,7 +105,7 @@ async def test_hpexpire_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 async def test_hpexpire_nonexistent_key_or_field(r):
     await r.delete("test:hash")
-    assert await r.hpexpire("test:hash", 500, "field1") == []
+    assert await r.hpexpire("test:hash", 500, "field1") == [-2]
     await r.hset("test:hash", "field1", "value1")
     assert await r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]
 
@@ -163,7 +163,7 @@ async def test_hexpireat_conditions(r):
 async def test_hexpireat_nonexistent_key_or_field(r):
     await r.delete("test:hash")
     future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
-    assert await r.hexpireat("test:hash", future_exp_time, "field1") == []
+    assert await r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
     await r.hset("test:hash", "field1", "value1")
     assert await r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
 
@@ -228,7 +228,7 @@ async def test_hpexpireat_nonexistent_key_or_field(r):
     future_exp_time = int(
         (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
     )
-    assert await r.hpexpireat("test:hash", future_exp_time, "field1") == []
+    assert await r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
     await r.hset("test:hash", "field1", "value1")
     assert await r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
 
diff --git a/tests/test_asyncio/test_json.py b/tests/test_asyncio/test_json.py
index 507afc56..852fd4aa 100644
--- a/tests/test_asyncio/test_json.py
+++ b/tests/test_asyncio/test_json.py
@@ -131,7 +131,7 @@ async def test_mset(decoded_r: redis.Redis):
 async def test_clear(decoded_r: redis.Redis):
     await decoded_r.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
     assert 1 == await decoded_r.json().clear("arr", Path.root_path())
-    assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [[[]]])
+    assert_resp_response(decoded_r, await decoded_r.json().get("arr"), [], [])
 
 
 @pytest.mark.redismod
diff --git a/tests/test_connection.py b/tests/test_connection.py
index bff24955..69275d58 100644
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -296,3 +296,53 @@ def test_redis_from_pool(request, from_url):
 
     assert called == 1
     pool.disconnect()
+
+
+@pytest.mark.parametrize(
+    "conn, error, expected_message",
+    [
+        (SSLConnection(), OSError(), "Error connecting to localhost:6379."),
+        (SSLConnection(), OSError(12), "Error 12 connecting to localhost:6379."),
+        (
+            SSLConnection(),
+            OSError(12, "Some Error"),
+            "Error 12 connecting to localhost:6379. Some Error.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(),
+            "Error connecting to unix:///tmp/redis.sock.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(12),
+            "Error 12 connecting to unix:///tmp/redis.sock.",
+        ),
+        (
+            UnixDomainSocketConnection(path="unix:///tmp/redis.sock"),
+            OSError(12, "Some Error"),
+            "Error 12 connecting to unix:///tmp/redis.sock. Some Error.",
+        ),
+    ],
+)
+def test_format_error_message(conn, error, expected_message):
+    """Test that the _error_message function formats errors correctly"""
+    error_message = conn._error_message(error)
+    assert error_message == expected_message
+
+
+def test_network_connection_failure():
+    with pytest.raises(ConnectionError) as e:
+        redis = Redis(port=9999)
+        redis.set("a", "b")
+    assert str(e.value) == "Error 111 connecting to localhost:9999. Connection refused."
+
+
+def test_unix_socket_connection_failure():
+    with pytest.raises(ConnectionError) as e:
+        redis = Redis(unix_socket_path="unix:///tmp/a.sock")
+        redis.set("a", "b")
+    assert (
+        str(e.value)
+        == "Error 2 connecting to unix:///tmp/a.sock. No such file or directory."
+    )
diff --git a/tests/test_hash.py b/tests/test_hash.py
index 7145b10a..9ed5e981 100644
--- a/tests/test_hash.py
+++ b/tests/test_hash.py
@@ -46,7 +46,7 @@ def test_hexpire_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hexpire_nonexistent_key_or_field(r):
     r.delete("test:hash")
-    assert r.hexpire("test:hash", 1, "field1") == []
+    assert r.hexpire("test:hash", 1, "field1") == [-2]
     r.hset("test:hash", "field1", "value1")
     assert r.hexpire("test:hash", 1, "nonexistent_field") == [-2]
 
@@ -115,7 +115,7 @@ def test_hpexpire_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hpexpire_nonexistent_key_or_field(r):
     r.delete("test:hash")
-    assert r.hpexpire("test:hash", 500, "field1") == []
+    assert r.hpexpire("test:hash", 500, "field1") == [-2]
     r.hset("test:hash", "field1", "value1")
     assert r.hpexpire("test:hash", 500, "nonexistent_field") == [-2]
 
@@ -182,7 +182,7 @@ def test_hexpireat_conditions(r):
 def test_hexpireat_nonexistent_key_or_field(r):
     r.delete("test:hash")
     future_exp_time = int((datetime.now() + timedelta(seconds=1)).timestamp())
-    assert r.hexpireat("test:hash", future_exp_time, "field1") == []
+    assert r.hexpireat("test:hash", future_exp_time, "field1") == [-2]
     r.hset("test:hash", "field1", "value1")
     assert r.hexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
 
@@ -257,7 +257,7 @@ def test_hpexpireat_nonexistent_key_or_field(r):
     future_exp_time = int(
         (datetime.now() + timedelta(milliseconds=500)).timestamp() * 1000
     )
-    assert r.hpexpireat("test:hash", future_exp_time, "field1") == []
+    assert r.hpexpireat("test:hash", future_exp_time, "field1") == [-2]
     r.hset("test:hash", "field1", "value1")
     assert r.hpexpireat("test:hash", future_exp_time, "nonexistent_field") == [-2]
 
@@ -298,7 +298,7 @@ def test_hpersist_multiple_fields(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hpersist_nonexistent_key(r):
     r.delete("test:hash")
-    assert r.hpersist("test:hash", "field1", "field2", "field3") == []
+    assert r.hpersist("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
 
 
 @skip_if_server_version_lt("7.3.240")
@@ -315,7 +315,7 @@ def test_hexpiretime_multiple_fields_mixed_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hexpiretime_nonexistent_key(r):
     r.delete("test:hash")
-    assert r.hexpiretime("test:hash", "field1", "field2", "field3") == []
+    assert r.hexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
 
 
 @skip_if_server_version_lt("7.3.240")
@@ -332,7 +332,7 @@ def test_hpexpiretime_multiple_fields_mixed_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hpexpiretime_nonexistent_key(r):
     r.delete("test:hash")
-    assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == []
+    assert r.hpexpiretime("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
 
 
 @skip_if_server_version_lt("7.3.240")
@@ -349,7 +349,7 @@ def test_httl_multiple_fields_mixed_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_httl_nonexistent_key(r):
     r.delete("test:hash")
-    assert r.httl("test:hash", "field1", "field2", "field3") == []
+    assert r.httl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
 
 
 @skip_if_server_version_lt("7.3.240")
@@ -366,4 +366,4 @@ def test_hpttl_multiple_fields_mixed_conditions(r):
 @skip_if_server_version_lt("7.3.240")
 def test_hpttl_nonexistent_key(r):
     r.delete("test:hash")
-    assert r.hpttl("test:hash", "field1", "field2", "field3") == []
+    assert r.hpttl("test:hash", "field1", "field2", "field3") == [-2, -2, -2]
diff --git a/tests/test_json.py b/tests/test_json.py
index a6884648..50787c71 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -130,7 +130,7 @@ def test_mset(client):
 def test_clear(client):
     client.json().set("arr", Path.root_path(), [0, 1, 2, 3, 4])
     assert 1 == client.json().clear("arr", Path.root_path())
-    assert_resp_response(client, client.json().get("arr"), [], [[[]]])
+    assert_resp_response(client, client.json().get("arr"), [], [])
 
 
 @pytest.mark.redismod
diff --git a/tests/test_search.py b/tests/test_search.py
index 284bccd9..a61f361c 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -2369,27 +2369,27 @@ def test_search_missing_fields(client):
 
     with pytest.raises(redis.exceptions.ResponseError) as e:
         client.ft().search(
-            Query("ismissing(@title)").dialect(5).return_field("id").no_content()
+            Query("ismissing(@title)").dialect(2).return_field("id").no_content()
         )
     assert "to be defined with 'INDEXMISSING'" in e.value.args[0]
 
     res = client.ft().search(
-        Query("ismissing(@features)").dialect(5).return_field("id").no_content()
+        Query("ismissing(@features)").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:2"])
 
     res = client.ft().search(
-        Query("-ismissing(@features)").dialect(5).return_field("id").no_content()
+        Query("-ismissing(@features)").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:1", "property:3"])
 
     res = client.ft().search(
-        Query("ismissing(@description)").dialect(5).return_field("id").no_content()
+        Query("ismissing(@description)").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:3"])
 
     res = client.ft().search(
-        Query("-ismissing(@description)").dialect(5).return_field("id").no_content()
+        Query("-ismissing(@description)").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:1", "property:2"])
 
@@ -2438,27 +2438,29 @@ def test_search_empty_fields(client):
 
     with pytest.raises(redis.exceptions.ResponseError) as e:
         client.ft().search(
-            Query("@title:''").dialect(5).return_field("id").no_content()
+            Query("@title:''").dialect(2).return_field("id").no_content()
         )
-    assert "to be defined with `INDEXEMPTY`" in e.value.args[0]
+    assert "Use `INDEXEMPTY` in field creation" in e.value.args[0]
 
     res = client.ft().search(
-        Query("@features:{ }").dialect(5).return_field("id").no_content()
+        Query("@features:{$empty}").dialect(2).return_field("id").no_content(),
+        query_params={"empty": ""},
     )
     _assert_search_result(client, res, ["property:2"])
 
     res = client.ft().search(
-        Query("-@features:{ }").dialect(5).return_field("id").no_content()
+        Query("-@features:{$empty}").dialect(2).return_field("id").no_content(),
+        query_params={"empty": ""},
     )
     _assert_search_result(client, res, ["property:1", "property:3"])
 
     res = client.ft().search(
-        Query("@description:''").dialect(5).return_field("id").no_content()
+        Query("@description:''").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:3"])
 
     res = client.ft().search(
-        Query("-@description:''").dialect(5).return_field("id").no_content()
+        Query("-@description:''").dialect(2).return_field("id").no_content()
     )
     _assert_search_result(client, res, ["property:1", "property:2"])
 
@@ -2503,14 +2505,13 @@ def test_special_characters_in_fields(client):
     )
     _assert_search_result(client, res, ["resource:1"])
 
-    # with dialect 5 no need to escape the - even without params
+    # with double quotes exact match no need to escape the - even without params
     res = client.ft().search(
-        Query("@uuid:{123e4567-e89b-12d3-a456-426614174000}").dialect(5)
+        Query('@uuid:{"123e4567-e89b-12d3-a456-426614174000"}').dialect(2)
     )
     _assert_search_result(client, res, ["resource:1"])
 
-    # also no need to escape ' with dialect 5
-    res = client.ft().search(Query("@tags:{new-year's-resolutions}").dialect(5))
+    res = client.ft().search(Query('@tags:{"new-year\'s-resolutions"}').dialect(2))
     _assert_search_result(client, res, ["resource:2"])
 
     # possible to search numeric fields by single value
@@ -2518,7 +2519,7 @@ def test_special_characters_in_fields(client):
     _assert_search_result(client, res, ["resource:2"])
 
     # some chars still need escaping
-    res = client.ft().search(Query(r"@tags:{\$btc}").dialect(5))
+    res = client.ft().search(Query(r"@tags:{\$btc}").dialect(2))
     _assert_search_result(client, res, ["resource:1"])