Skip to content

Commit b6e40b9

Browse files
committed
add tests
1 parent 698176d commit b6e40b9

File tree

1 file changed

+168
-1
lines changed

1 file changed

+168
-1
lines changed

tests/test_asyncio/test_sentinel_managed_connection.py

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import socket
22

33
import pytest
4+
import pytest_asyncio
45
from redis.asyncio.retry import Retry
5-
from redis.asyncio.sentinel import SentinelManagedConnection
6+
from redis.asyncio.sentinel import SentinelManagedConnection, SentinelConnectionPool, Sentinel
67
from redis.backoff import NoBackoff
8+
from unittest.mock import AsyncMock
9+
from typing import AsyncIterator
10+
11+
import pytest
712

813
from .compat import mock
914

@@ -35,3 +40,165 @@ async def mock_connect():
3540
await conn.connect()
3641
assert conn._connect.call_count == 3
3742
await conn.disconnect()
43+
44+
45+
def same_address(
46+
connection_1: SentinelManagedConnection,
47+
connection_2: SentinelManagedConnection,
48+
) -> bool:
49+
return bool(
50+
connection_1.host == connection_2.host and connection_1.port == connection_2.port
51+
)
52+
53+
class SentinelManagedConnectionMock(SentinelManagedConnection):
54+
async def connect_to_address(self, host: str, port: int) -> None:
55+
self.host = host
56+
self.port = port
57+
58+
async def can_read_destructive(self) -> bool:
59+
# Mock this function to always return False.
60+
# Trrue means there's still data to be read and hence we can't reconnect
61+
# to this connection yet
62+
return False
63+
64+
65+
class SentinelManagedConnectionMockForReplicaMode(SentinelManagedConnectionMock):
66+
async def connect(self) -> None:
67+
"""
68+
This simulates the behavior of connect when the ``redis.asyncio.sentinel.SentinelConnectionPool``
69+
is in replica mode.
70+
It'll call rotate_slaves and connect to the next replica
71+
"""
72+
import random
73+
import time
74+
75+
self.host = f"host-{random.randint(0, 10)}"
76+
self.port = time.time()
77+
78+
79+
class SentinelManagedConnectionMockForMasterMode(SentinelManagedConnectionMock):
80+
async def connect_to(self, address: tuple[str, int]) -> None:
81+
"""
82+
This simulates the behavior of connect_to when the ``redis.asyncio.sentinel.SentinelConnectionPool``
83+
is in master mode.
84+
It'll try to connect to master but we should not create any connection in test,
85+
so just set the host and port without actually connecting.
86+
"""
87+
self.host, self.port = address
88+
89+
90+
@pytest_asyncio.fixture()
91+
async def connection_pool_replica_mock() -> SentinelConnectionPool:
92+
sentinel_manager = Sentinel([["master", 400]])
93+
# Give a random slave
94+
sentinel_manager.discover_slaves = AsyncMock(return_value=["replica", 5000]) # type: ignore[method-assign]
95+
# Create connection pool with our mock connection object
96+
connection_pool = SentinelConnectionPool(
97+
"usasm",
98+
sentinel_manager,
99+
is_master=False,
100+
connection_class=SentinelManagedConnectionMockForReplicaMode,
101+
)
102+
return connection_pool
103+
104+
105+
@pytest_asyncio.fixture()
106+
async def connection_pool_master_mock() -> SentinelConnectionPool:
107+
sentinel_manager = Sentinel([["master", 400]])
108+
# Give a random slave
109+
sentinel_manager.discover_master = AsyncMock(return_value=["replica", 5000]) # type: ignore[method-assign]
110+
# Create connection pool with our mock connection object
111+
connection_pool = SentinelConnectionPool(
112+
"usasm",
113+
sentinel_manager,
114+
is_master=True,
115+
connection_class=SentinelManagedConnectionMockForMasterMode,
116+
)
117+
return connection_pool
118+
119+
120+
async def test_connection_pool_connects_to_same_address_if_same_iter_req_id_in_replica_mode(
121+
connection_pool_replica_mock: SentinelConnectionPool,
122+
) -> None:
123+
# Assert that the connection address is the same if the _iter_req_id is the same
124+
connection_for_req_1 = await connection_pool_replica_mock.get_connection("ANY", _iter_req_id=1)
125+
assert same_address(
126+
await connection_pool_replica_mock.get_connection(
127+
"ANY", _iter_req_id=1
128+
),
129+
connection_for_req_1,
130+
)
131+
132+
133+
async def test_connection_pool_returns_same_conn_object_if_same_iter_req_id_and_released_in_replica_mode(
134+
connection_pool_replica_mock: SentinelConnectionPool,
135+
) -> None:
136+
# Assert that the connection object is the same if the _iter_req_id is the same
137+
connection_for_req_1 = await connection_pool_replica_mock.get_connection("ANY", _iter_req_id=1)
138+
await connection_pool_replica_mock.release(connection_for_req_1)
139+
assert (
140+
await connection_pool_replica_mock.get_connection("ANY", _iter_req_id=1)
141+
== connection_for_req_1
142+
)
143+
144+
145+
async def test_connection_pool_connects_to_diff_address_if_no_iter_req_id_in_replica_mode(
146+
connection_pool_replica_mock: SentinelConnectionPool,
147+
) -> None:
148+
# Assert that the connection object is different if no _iter_req_id is supplied
149+
# In reality, they can be the same, but in this case, we're not releasing the connection
150+
# to the pool so they should always be different.
151+
connection_for_req_1 = await connection_pool_replica_mock.get_connection("ANY", _iter_req_id=1)
152+
connection_for_random_req = await connection_pool_replica_mock.get_connection("ANYY")
153+
assert not same_address(connection_for_random_req, connection_for_req_1)
154+
assert not same_address(
155+
await connection_pool_replica_mock.get_connection("ANY_COMMAND"),
156+
connection_for_random_req,
157+
)
158+
assert not same_address(
159+
await connection_pool_replica_mock.get_connection(
160+
"ANY_COMMAND"
161+
),
162+
connection_for_req_1,
163+
)
164+
165+
166+
async def test_connection_pool_connects_to_same_address_if_same_iter_req_id_in_master_mode(
167+
connection_pool_master_mock: SentinelConnectionPool,
168+
) -> None:
169+
# Assert that the connection address is the same if the _iter_req_id is the same
170+
connection_for_req_1 = await connection_pool_master_mock.get_connection("ANY", _iter_req_id=1)
171+
assert same_address(
172+
await connection_pool_master_mock.get_connection(
173+
"ANY", _iter_req_id=1
174+
),
175+
connection_for_req_1,
176+
)
177+
178+
179+
async def test_connection_pool_returns_same_conn_object_if_same_iter_req_id_and_released_in_master_mode(
180+
connection_pool_master_mock: SentinelConnectionPool,
181+
) -> None:
182+
# Assert that the connection address is the same if the _iter_req_id is the same
183+
connection_for_req_1 = await connection_pool_master_mock.get_connection("ANY", _iter_req_id=1)
184+
assert same_address(
185+
await connection_pool_master_mock.get_connection("ANY", _iter_req_id=1),
186+
connection_for_req_1,
187+
)
188+
189+
async def test_connection_pool_connects_to_same_address_if_no_iter_req_id_in_master_mode(
190+
connection_pool_master_mock: SentinelConnectionPool,
191+
) -> None:
192+
# Assert that connection address is always the same regardless if it's an iter command or not
193+
connection_for_req_1 = await connection_pool_master_mock.get_connection("ANY", _iter_req_id=1)
194+
connection_for_random_req = await connection_pool_master_mock.get_connection("ANYY")
195+
assert same_address(connection_for_random_req, connection_for_req_1)
196+
assert same_address(
197+
await connection_pool_master_mock.get_connection("ANY_COMMAND"),
198+
connection_for_random_req
199+
)
200+
201+
assert same_address(
202+
await connection_pool_master_mock.get_connection("ANY_COMMAND"),
203+
connection_for_req_1,
204+
)

0 commit comments

Comments
 (0)