Repository: redis/redis-py

Issue:
WRONGPASS response doesn't raise AuthenticationError exception
**Version**: What redis-py and what redis version is the issue happening on?

redis-py 4.3.4
redis 6.3.6

**Platform**: What platform / version? (For example Python 3.5.1 on Windows 7 / Ubuntu 15.10 / Azure)

python 3.9 on macOS Monterey 12.0.1

**Description**: Description of your issue, stack traces from errors and code that reproduces the issue

I've expected that `WRONGPASS` response should raise `AuthenticationError` exception. But when I tried to catch it, it's just ResponseError. 
```
if __name__ == '__main__':
    redis = RedisClient(
        host="localhost",
        port=6379,
        socket_timeout=REDIS_SOCKET_TIMEOUT_SECONDS,
        retry_on_timeout=True,
        username="admin",
        # actual password is different
        password="random"
    )

    try:
        val = redis.get("test")
    except AuthenticationError as e:
        print("I can't reach that code")
        val = 0

    print(val)
```

When I debugged `connection.py` file, I guess that raising of `AuthenticationError` in case of `WRONGPASS` should occur [here](https://github.com/redis/redis-py/blob/e6cd4fdf3b159d7f118f154e28e884069da89d7e/redis/connection.py#L722):
```
            if str_if_bytes(auth_response) != "OK":
                raise AuthenticationError("Invalid Username or Password")
```

but this part of code can't be reached because of this [part](https://github.com/redis/redis-py/blob/e6cd4fdf3b159d7f118f154e28e884069da89d7e/redis/connection.py#L713):
```
auth_response = self.read_response()
```
It raises `ResponseError` which is not handled in any except block in function `on_connect`.



Discussion:

Patch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index 5876c08..56dc286 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -18,6 +18,15 @@ on:
 
 jobs:
 
+   dependency-audit:
+     name: Dependency audit
+     runs-on: ubuntu-latest
+     steps:
+       - uses: actions/checkout@v3
+       - uses: trailofbits/gh-action-pip-audit@v1.0.0
+         with:
+           inputs: requirements.txt dev_requirements.txt
+
    lint:
      name: Code linters
      runs-on: ubuntu-latest
diff --git a/LICENSE b/LICENSE
index 29a3fe3..00aee10 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,22 +1,21 @@
-Copyright (c) 2012 Andy McCurdy
+MIT License
 
- Permission is hereby granted, free of charge, to any person
- obtaining a copy of this software and associated documentation
- files (the "Software"), to deal in the Software without
- restriction, including without limitation the rights to use,
- copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following
- conditions:
+Copyright (c) 2022, Redis, inc.
 
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 80e2cc2..f5ad9a4 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ Alternatively, you might want to look at [Async connections](https://redis.readt
 
 ### Redis Commands
 
-There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) except where a work (i.e del) is reserved by the language. The complete set of commands can be found [here](https://github.com/redis/redis-py/tree/master/redis/commands), or [the documentation](https://redis.readthedocs.io/en/stable/commands.html).
+There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) except where a word (i.e del) is reserved by the language. The complete set of commands can be found [here](https://github.com/redis/redis-py/tree/master/redis/commands), or [the documentation](https://redis.readthedocs.io/en/stable/commands.html).
 
 ## Advanced Topics
 
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
index 6823dcb..db8c240 100644
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -87,6 +87,15 @@ MODULE_EXPORTS_DATA_TYPES_ERROR = (
     "exports one or more module-side data "
     "types, can't unload"
 )
+# user send an AUTH cmd to a server without authorization configured
+NO_AUTH_SET_ERROR = {
+    # Redis >= 6.0
+    "AUTH <password> called without any password "
+    "configured for the default user. Are you sure "
+    "your configuration is correct?": AuthenticationError,
+    # Redis < 6.0
+    "Client sent AUTH, but no password is set": AuthenticationError,
+}
 
 
 class _HiredisReaderArgs(TypedDict, total=False):
@@ -160,7 +169,9 @@ class BaseParser:
             MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError,
             NO_SUCH_MODULE_ERROR: ModuleError,
             MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
+            **NO_AUTH_SET_ERROR,
         },
+        "WRONGPASS": AuthenticationError,
         "EXECABORT": ExecAbortError,
         "LOADING": BusyLoadingError,
         "NOSCRIPT": NoScriptError,
diff --git a/redis/commands/bf/commands.py b/redis/commands/bf/commands.py
index 0fe065d..2b16139 100644
--- a/redis/commands/bf/commands.py
+++ b/redis/commands/bf/commands.py
@@ -351,7 +351,7 @@ class TDigestCommands:
         Allocate the memory and initialize the t-digest.
         For more information see `TDIGEST.CREATE <https://redis.io/commands/tdigest.create>`_.
         """  # noqa
-        return self.execute_command(TDIGEST_CREATE, key, compression)
+        return self.execute_command(TDIGEST_CREATE, key, "COMPRESSION", compression)
 
     def reset(self, key):
         """
diff --git a/redis/commands/core.py b/redis/commands/core.py
index 6cf0457..51f30d2 100644
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -1504,6 +1504,29 @@ class BasicKeyCommands(CommandsProtocol):
         """
         return BitFieldOperation(self, key, default_overflow=default_overflow)
 
+    def bitfield_ro(
+        self: Union["Redis", "AsyncRedis"],
+        key: KeyT,
+        encoding: str,
+        offset: BitfieldOffsetT,
+        items: Optional[list] = None,
+    ) -> ResponseT:
+        """
+        Return an array of the specified bitfield values
+        where the first value is found using ``encoding`` and ``offset``
+        parameters and remaining values are result of corresponding
+        encoding/offset pairs in optional list ``items``
+        Read-only variant of the BITFIELD command.
+
+        For more information see https://redis.io/commands/bitfield_ro
+        """
+        params = [key, "GET", encoding, offset]
+
+        items = items or []
+        for encoding, offset in items:
+            params.extend(["GET", encoding, offset])
+        return self.execute_command("BITFIELD_RO", *params)
+
     def bitop(self, operation: str, dest: KeyT, *keys: KeyT) -> ResponseT:
         """
         Perform a bitwise operation using ``operation`` between ``keys`` and
diff --git a/redis/connection.py b/redis/connection.py
index 3438baf..3281b14 100755
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -80,6 +80,15 @@ MODULE_EXPORTS_DATA_TYPES_ERROR = (
     "exports one or more module-side data "
     "types, can't unload"
 )
+# user send an AUTH cmd to a server without authorization configured
+NO_AUTH_SET_ERROR = {
+    # Redis >= 6.0
+    "AUTH <password> called without any password "
+    "configured for the default user. Are you sure "
+    "your configuration is correct?": AuthenticationError,
+    # Redis < 6.0
+    "Client sent AUTH, but no password is set": AuthenticationError,
+}
 
 
 class Encoder:
@@ -127,7 +136,6 @@ class BaseParser:
     EXCEPTION_CLASSES = {
         "ERR": {
             "max number of clients reached": ConnectionError,
-            "Client sent AUTH, but no password is set": AuthenticationError,
             "invalid password": AuthenticationError,
             # some Redis server versions report invalid command syntax
             # in lowercase
@@ -141,7 +149,9 @@ class BaseParser:
             MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError,
             NO_SUCH_MODULE_ERROR: ModuleError,
             MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
+            **NO_AUTH_SET_ERROR,
         },
+        "WRONGPASS": AuthenticationError,
         "EXECABORT": ExecAbortError,
         "LOADING": BusyLoadingError,
         "NOSCRIPT": NoScriptError,
@@ -1166,6 +1176,16 @@ URL_QUERY_ARGUMENT_PARSERS = {
 
 
 def parse_url(url):
+    if not (
+        url.startswith("redis://")
+        or url.startswith("rediss://")
+        or url.startswith("unix://")
+    ):
+        raise ValueError(
+            "Redis URL must specify one of the following "
+            "schemes (redis://, rediss://, unix://)"
+        )
+
     url = urlparse(url)
     kwargs = {}
 
@@ -1192,7 +1212,7 @@ def parse_url(url):
             kwargs["path"] = unquote(url.path)
         kwargs["connection_class"] = UnixDomainSocketConnection
 
-    elif url.scheme in ("redis", "rediss"):
+    else:  # implied:  url.scheme in ("redis", "rediss"):
         if url.hostname:
             kwargs["host"] = unquote(url.hostname)
         if url.port:
@@ -1208,11 +1228,6 @@ def parse_url(url):
 
         if url.scheme == "rediss":
             kwargs["connection_class"] = SSLConnection
-    else:
-        raise ValueError(
-            "Redis URL must specify one of the following "
-            "schemes (redis://, rediss://, unix://)"
-        )
 
     return kwargs
 
diff --git a/redis/typing.py b/redis/typing.py
index b572b0c..8504c7d 100644
--- a/redis/typing.py
+++ b/redis/typing.py
@@ -16,7 +16,7 @@ EncodedT = Union[bytes, memoryview]
 DecodedT = Union[str, int, float]
 EncodableT = Union[EncodedT, DecodedT]
 AbsExpiryT = Union[int, datetime]
-ExpiryT = Union[float, timedelta]
+ExpiryT = Union[int, timedelta]
 ZScoreBoundT = Union[float, str]  # str allows for the [ or ( prefix
 BitfieldOffsetT = Union[int, str]  # str allows for #x syntax
 _StringLikeT = Union[bytes, str, memoryview]


Test patch:
diff --git a/tests/test_asyncio/test_bloom.py b/tests/test_asyncio/test_bloom.py
index 15b22b3..8e642d2 100644
--- a/tests/test_asyncio/test_bloom.py
+++ b/tests/test_asyncio/test_bloom.py
@@ -366,21 +366,21 @@ async def test_tdigest_quantile(modclient: redis.Redis):
     # assert min min/max have same result as quantile 0 and 1
     assert (
         await modclient.tdigest().max("tDigest")
-        == (await modclient.tdigest().quantile("tDigest", 1.0))[1]
+        == (await modclient.tdigest().quantile("tDigest", 1.0))[0]
     )
     assert (
         await modclient.tdigest().min("tDigest")
-        == (await modclient.tdigest().quantile("tDigest", 0.0))[1]
+        == (await modclient.tdigest().quantile("tDigest", 0.0))[0]
     )
 
-    assert 1.0 == round((await modclient.tdigest().quantile("tDigest", 0.01))[1], 2)
-    assert 99.0 == round((await modclient.tdigest().quantile("tDigest", 0.99))[1], 2)
+    assert 1.0 == round((await modclient.tdigest().quantile("tDigest", 0.01))[0], 2)
+    assert 99.0 == round((await modclient.tdigest().quantile("tDigest", 0.99))[0], 2)
 
     # test multiple quantiles
     assert await modclient.tdigest().create("t-digest", 100)
     assert await modclient.tdigest().add("t-digest", [1, 2, 3, 4, 5], [1.0] * 5)
     res = await modclient.tdigest().quantile("t-digest", 0.5, 0.8)
-    assert [0.5, 3.0, 0.8, 5.0] == res
+    assert [3.0, 5.0] == res
 
 
 @pytest.mark.redismod
diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py
index 4c25277..422328a 100644
--- a/tests/test_asyncio/test_commands.py
+++ b/tests/test_asyncio/test_commands.py
@@ -2953,6 +2953,19 @@ class TestRedisCommands:
         )
         assert resp == [0, None, 255]
 
+    @skip_if_server_version_lt("6.0.0")
+    async def test_bitfield_ro(self, r: redis.Redis):
+        bf = r.bitfield("a")
+        resp = await bf.set("u8", 8, 255).execute()
+        assert resp == [0]
+
+        resp = await r.bitfield_ro("a", "u8", 0)
+        assert resp == [0]
+
+        items = [("u4", 8), ("u4", 12), ("u4", 13)]
+        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: redis.Redis):
         # put a key into the current db to make sure that "db.<current-db>"
diff --git a/tests/test_asyncio/test_graph.py b/tests/test_asyncio/test_graph.py
index 8a8f9cf..0c011bd 100644
--- a/tests/test_asyncio/test_graph.py
+++ b/tests/test_asyncio/test_graph.py
@@ -348,11 +348,11 @@ async def test_list_keys(modclient: redis.Redis):
     result = await modclient.graph().list_keys()
     assert result == []
 
-    await modclient.execute_command("GRAPH.EXPLAIN", "G", "RETURN 1")
+    await modclient.graph("G").query("CREATE (n)")
     result = await modclient.graph().list_keys()
     assert result == ["G"]
 
-    await modclient.execute_command("GRAPH.EXPLAIN", "X", "RETURN 1")
+    await modclient.graph("X").query("CREATE (m)")
     result = await modclient.graph().list_keys()
     assert result == ["G", "X"]
 
diff --git a/tests/test_asyncio/test_timeseries.py b/tests/test_asyncio/test_timeseries.py
index 182f6b5..ef58226 100644
--- a/tests/test_asyncio/test_timeseries.py
+++ b/tests/test_asyncio/test_timeseries.py
@@ -240,6 +240,9 @@ async def test_range_advanced(modclient: redis.Redis):
     assert [(0, 5.0), (5, 6.0)] == await modclient.ts().range(
         1, 0, 10, aggregation_type="count", bucket_size_msec=10, align=5
     )
+    assert [(0, 2.5500000000000003), (10, 3.0)] == await modclient.ts().range(
+        1, 0, 10, aggregation_type="twa", bucket_size_msec=10
+    )
 
 
 @pytest.mark.redismod
diff --git a/tests/test_bloom.py b/tests/test_bloom.py
index 12a4839..9a3e4a4 100644
--- a/tests/test_bloom.py
+++ b/tests/test_bloom.py
@@ -365,17 +365,17 @@ def test_tdigest_quantile(client):
     )
     # assert min min/max have same result as quantile 0 and 1
     res = client.tdigest().quantile("tDigest", 1.0)
-    assert client.tdigest().max("tDigest") == res[1]
+    assert client.tdigest().max("tDigest") == res[0]
     res = client.tdigest().quantile("tDigest", 0.0)
-    assert client.tdigest().min("tDigest") == res[1]
+    assert client.tdigest().min("tDigest") == res[0]
 
-    assert 1.0 == round(client.tdigest().quantile("tDigest", 0.01)[1], 2)
-    assert 99.0 == round(client.tdigest().quantile("tDigest", 0.99)[1], 2)
+    assert 1.0 == round(client.tdigest().quantile("tDigest", 0.01)[0], 2)
+    assert 99.0 == round(client.tdigest().quantile("tDigest", 0.99)[0], 2)
 
     # test multiple quantiles
     assert client.tdigest().create("t-digest", 100)
     assert client.tdigest().add("t-digest", [1, 2, 3, 4, 5], [1.0] * 5)
-    assert [0.5, 3.0, 0.8, 5.0] == client.tdigest().quantile("t-digest", 0.5, 0.8)
+    assert [3.0, 5.0] == client.tdigest().quantile("t-digest", 0.5, 0.8)
 
 
 @pytest.mark.redismod
diff --git a/tests/test_commands.py b/tests/test_commands.py
index f9134d8..1c9a5c2 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -68,6 +68,14 @@ class TestResponseCallbacks:
 class TestRedisCommands:
     @skip_if_redis_enterprise()
     def test_auth(self, r, request):
+        # sending an AUTH command before setting a user/password on the
+        # server should return an AuthenticationError
+        with pytest.raises(exceptions.AuthenticationError):
+            r.auth("some_password")
+
+        with pytest.raises(exceptions.AuthenticationError):
+            r.auth("some_password", "some_user")
+
         # first, test for default user (`username` is supposed to be optional)
         default_username = "default"
         temp_pass = "temp_pass"
@@ -81,9 +89,19 @@ class TestRedisCommands:
 
         def teardown():
             try:
-                r.auth(temp_pass)
-            except exceptions.ResponseError:
-                r.auth("default", "")
+                # this is needed because after an AuthenticationError the connection
+                # is closed, and if we send an AUTH command a new connection is
+                # created, but in this case we'd get an "Authentication required"
+                # error when switching to the db 9 because we're not authenticated yet
+                # setting the password on the connection itself triggers the
+                # authentication in the connection's `on_connect` method
+                r.connection.password = temp_pass
+            except AttributeError:
+                # connection field is not set in Redis Cluster, but that's ok
+                # because the problem discussed above does not apply to Redis Cluster
+                pass
+
+            r.auth(temp_pass)
             r.config_set("requirepass", "")
             r.acl_deluser(username)
 
@@ -95,7 +113,7 @@ class TestRedisCommands:
 
         assert r.auth(username=username, password="strong_password") is True
 
-        with pytest.raises(exceptions.ResponseError):
+        with pytest.raises(exceptions.AuthenticationError):
             r.auth(username=username, password="wrong_password")
 
     def test_command_on_invalid_key_type(self, r):
@@ -4438,6 +4456,19 @@ class TestRedisCommands:
         )
         assert resp == [0, None, 255]
 
+    @skip_if_server_version_lt("6.0.0")
+    def test_bitfield_ro(self, r: redis.Redis):
+        bf = r.bitfield("a")
+        resp = bf.set("u8", 8, 255).execute()
+        assert resp == [0]
+
+        resp = r.bitfield_ro("a", "u8", 0)
+        assert resp == [0]
+
+        items = [("u4", 8), ("u4", 12), ("u4", 13)]
+        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):
diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py
index a836f5b..73eb6e1 100644
--- a/tests/test_connection_pool.py
+++ b/tests/test_connection_pool.py
@@ -339,6 +339,14 @@ class TestConnectionPoolURLParsing:
             "(redis://, rediss://, unix://)"
         )
 
+    def test_invalid_scheme_raises_error_when_double_slash_missing(self):
+        with pytest.raises(ValueError) as cm:
+            redis.ConnectionPool.from_url("redis:foo.bar.com:12345")
+        assert str(cm.value) == (
+            "Redis URL must specify one of the following schemes "
+            "(redis://, rediss://, unix://)"
+        )
+
 
 class TestConnectionPoolUnixSocketURLParsing:
     def test_defaults(self):
@@ -545,22 +553,40 @@ class TestConnection:
         )
 
     @skip_if_redis_enterprise()
-    def test_connect_no_auth_supplied_when_required(self, r):
+    def test_connect_no_auth_configured(self, r):
         """
-        AuthenticationError should be raised when the server requires a
-        password but one isn't supplied.
+        AuthenticationError should be raised when the server is not configured with auth
+        but credentials are supplied by the user.
         """
+        # Redis < 6
         with pytest.raises(redis.AuthenticationError):
             r.execute_command(
                 "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set"
             )
 
+        # Redis >= 6
+        with pytest.raises(redis.AuthenticationError):
+            r.execute_command(
+                "DEBUG",
+                "ERROR",
+                "ERR AUTH <password> called without any password "
+                "configured for the default user. Are you sure "
+                "your configuration is correct?",
+            )
+
     @skip_if_redis_enterprise()
-    def test_connect_invalid_password_supplied(self, r):
-        "AuthenticationError should be raised when sending the wrong password"
+    def test_connect_invalid_auth_credentials_supplied(self, r):
+        """
+        AuthenticationError should be raised when sending invalid username/password
+        """
+        # Redis < 6
         with pytest.raises(redis.AuthenticationError):
             r.execute_command("DEBUG", "ERROR", "ERR invalid password")
 
+        # Redis >= 6
+        with pytest.raises(redis.AuthenticationError):
+            r.execute_command("DEBUG", "ERROR", "WRONGPASS")
+
 
 @pytest.mark.onlynoncluster
 class TestMultiConnectionClient:
diff --git a/tests/test_graph.py b/tests/test_graph.py
index 526308c..a46c336 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -366,11 +366,11 @@ def test_list_keys(client):
     result = client.graph().list_keys()
     assert result == []
 
-    client.execute_command("GRAPH.EXPLAIN", "G", "RETURN 1")
+    client.graph("G").query("CREATE (n)")
     result = client.graph().list_keys()
     assert result == ["G"]
 
-    client.execute_command("GRAPH.EXPLAIN", "X", "RETURN 1")
+    client.graph("X").query("CREATE (m)")
     result = client.graph().list_keys()
     assert result == ["G", "X"]
 
diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py
index b4b85e1..cb27a75 100644
--- a/tests/test_timeseries.py
+++ b/tests/test_timeseries.py
@@ -233,7 +233,7 @@ def test_range_advanced(client):
     assert [(0, 5.0), (5, 6.0)] == client.ts().range(
         1, 0, 10, aggregation_type="count", bucket_size_msec=10, align=5
     )
-    assert [(0, 2.5500000000000003), (10, 3.95)] == client.ts().range(
+    assert [(0, 2.5500000000000003), (10, 3.0)] == client.ts().range(
         1, 0, 10, aggregation_type="twa", bucket_size_msec=10
     )