This commit is contained in:
Timothy Jaeryang Baek
2026-03-17 17:58:01 -05:00
parent fcf7208352
commit de3317e26b
220 changed files with 17200 additions and 22836 deletions

View File

@@ -16,84 +16,84 @@ class TestSentinelRedisProxy:
def test_parse_redis_service_url_valid(self):
"""Test parsing valid Redis service URL"""
url = "redis://user:pass@mymaster:6379/0"
url = 'redis://user:pass@mymaster:6379/0'
result = parse_redis_service_url(url)
assert result["username"] == "user"
assert result["password"] == "pass"
assert result["service"] == "mymaster"
assert result["port"] == 6379
assert result["db"] == 0
assert result['username'] == 'user'
assert result['password'] == 'pass'
assert result['service'] == 'mymaster'
assert result['port'] == 6379
assert result['db'] == 0
def test_parse_redis_service_url_defaults(self):
"""Test parsing Redis service URL with defaults"""
url = "redis://mymaster"
url = 'redis://mymaster'
result = parse_redis_service_url(url)
assert result["username"] is None
assert result["password"] is None
assert result["service"] == "mymaster"
assert result["port"] == 6379
assert result["db"] == 0
assert result['username'] is None
assert result['password'] is None
assert result['service'] == 'mymaster'
assert result['port'] == 6379
assert result['db'] == 0
def test_parse_redis_service_url_invalid_scheme(self):
"""Test parsing invalid URL scheme"""
with pytest.raises(ValueError, match="Invalid Redis URL scheme"):
parse_redis_service_url("http://invalid")
with pytest.raises(ValueError, match='Invalid Redis URL scheme'):
parse_redis_service_url('http://invalid')
def test_get_sentinels_from_env(self):
"""Test parsing sentinel hosts from environment"""
hosts = "sentinel1,sentinel2,sentinel3"
port = "26379"
hosts = 'sentinel1,sentinel2,sentinel3'
port = '26379'
result = get_sentinels_from_env(hosts, port)
expected = [("sentinel1", 26379), ("sentinel2", 26379), ("sentinel3", 26379)]
expected = [('sentinel1', 26379), ('sentinel2', 26379), ('sentinel3', 26379)]
assert result == expected
def test_get_sentinels_from_env_empty(self):
"""Test empty sentinel hosts"""
result = get_sentinels_from_env(None, "26379")
result = get_sentinels_from_env(None, '26379')
assert result == []
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_sentinel_redis_proxy_sync_success(self, mock_sentinel_class):
"""Test successful sync operation with SentinelRedisProxy"""
mock_sentinel = Mock()
mock_master = Mock()
mock_master.get.return_value = "test_value"
mock_master.get.return_value = 'test_value'
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test attribute access
get_method = proxy.__getattr__("get")
result = get_method("test_key")
get_method = proxy.__getattr__('get')
result = get_method('test_key')
assert result == "test_value"
mock_sentinel.master_for.assert_called_with("mymaster")
mock_master.get.assert_called_with("test_key")
assert result == 'test_value'
mock_sentinel.master_for.assert_called_with('mymaster')
mock_master.get.assert_called_with('test_key')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_sentinel_redis_proxy_async_success(self, mock_sentinel_class):
"""Test successful async operation with SentinelRedisProxy"""
mock_sentinel = Mock()
mock_master = Mock()
mock_master.get = AsyncMock(return_value="test_value")
mock_master.get = AsyncMock(return_value='test_value')
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test async attribute access
get_method = proxy.__getattr__("get")
result = await get_method("test_key")
get_method = proxy.__getattr__('get')
result = await get_method('test_key')
assert result == "test_value"
mock_sentinel.master_for.assert_called_with("mymaster")
mock_master.get.assert_called_with("test_key")
assert result == 'test_value'
mock_sentinel.master_for.assert_called_with('mymaster')
mock_master.get.assert_called_with('test_key')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_sentinel_redis_proxy_failover_retry(self, mock_sentinel_class):
"""Test retry mechanism during failover"""
mock_sentinel = Mock()
@@ -101,39 +101,39 @@ class TestSentinelRedisProxy:
# First call fails, second succeeds
mock_master.get.side_effect = [
redis.exceptions.ConnectionError("Master down"),
"test_value",
redis.exceptions.ConnectionError('Master down'),
'test_value',
]
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
get_method = proxy.__getattr__("get")
result = get_method("test_key")
get_method = proxy.__getattr__('get')
result = get_method('test_key')
assert result == "test_value"
assert result == 'test_value'
assert mock_master.get.call_count == 2
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_sentinel_redis_proxy_max_retries_exceeded(self, mock_sentinel_class):
"""Test failure after max retries exceeded"""
mock_sentinel = Mock()
mock_master = Mock()
# All calls fail
mock_master.get.side_effect = redis.exceptions.ConnectionError("Master down")
mock_master.get.side_effect = redis.exceptions.ConnectionError('Master down')
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
get_method = proxy.__getattr__("get")
get_method = proxy.__getattr__('get')
with pytest.raises(redis.exceptions.ConnectionError):
get_method("test_key")
get_method('test_key')
assert mock_master.get.call_count == MAX_RETRY_COUNT
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_sentinel_redis_proxy_readonly_error_retry(self, mock_sentinel_class):
"""Test retry on ReadOnlyError"""
mock_sentinel = Mock()
@@ -141,20 +141,20 @@ class TestSentinelRedisProxy:
# First call gets ReadOnlyError (old master), second succeeds (new master)
mock_master.get.side_effect = [
redis.exceptions.ReadOnlyError("Read only"),
"test_value",
redis.exceptions.ReadOnlyError('Read only'),
'test_value',
]
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
get_method = proxy.__getattr__("get")
result = get_method("test_key")
get_method = proxy.__getattr__('get')
result = get_method('test_key')
assert result == "test_value"
assert result == 'test_value'
assert mock_master.get.call_count == 2
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_sentinel_redis_proxy_factory_methods(self, mock_sentinel_class):
"""Test factory methods are passed through directly"""
mock_sentinel = Mock()
@@ -163,61 +163,53 @@ class TestSentinelRedisProxy:
mock_master.pipeline.return_value = mock_pipeline
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Factory methods should be passed through without wrapping
pipeline_method = proxy.__getattr__("pipeline")
pipeline_method = proxy.__getattr__('pipeline')
result = pipeline_method()
assert result == mock_pipeline
mock_master.pipeline.assert_called_once()
@patch("redis.sentinel.Sentinel")
@patch("redis.from_url")
def test_get_redis_connection_with_sentinel(
self, mock_from_url, mock_sentinel_class
):
@patch('redis.sentinel.Sentinel')
@patch('redis.from_url')
def test_get_redis_connection_with_sentinel(self, mock_from_url, mock_sentinel_class):
"""Test getting Redis connection with Sentinel"""
mock_sentinel = Mock()
mock_sentinel_class.return_value = mock_sentinel
sentinels = [("sentinel1", 26379), ("sentinel2", 26379)]
redis_url = "redis://user:pass@mymaster:6379/0"
sentinels = [('sentinel1', 26379), ('sentinel2', 26379)]
redis_url = 'redis://user:pass@mymaster:6379/0'
result = get_redis_connection(
redis_url=redis_url, redis_sentinels=sentinels, async_mode=False
)
result = get_redis_connection(redis_url=redis_url, redis_sentinels=sentinels, async_mode=False)
assert isinstance(result, SentinelRedisProxy)
mock_sentinel_class.assert_called_once()
mock_from_url.assert_not_called()
@patch("redis.Redis.from_url")
@patch('redis.Redis.from_url')
def test_get_redis_connection_without_sentinel(self, mock_from_url):
"""Test getting Redis connection without Sentinel"""
mock_redis = Mock()
mock_from_url.return_value = mock_redis
redis_url = "redis://localhost:6379/0"
redis_url = 'redis://localhost:6379/0'
result = get_redis_connection(
redis_url=redis_url, redis_sentinels=None, async_mode=False
)
result = get_redis_connection(redis_url=redis_url, redis_sentinels=None, async_mode=False)
assert result == mock_redis
mock_from_url.assert_called_once_with(redis_url, decode_responses=True)
@patch("redis.asyncio.from_url")
@patch('redis.asyncio.from_url')
def test_get_redis_connection_without_sentinel_async(self, mock_from_url):
"""Test getting async Redis connection without Sentinel"""
mock_redis = Mock()
mock_from_url.return_value = mock_redis
redis_url = "redis://localhost:6379/0"
redis_url = 'redis://localhost:6379/0'
result = get_redis_connection(
redis_url=redis_url, redis_sentinels=None, async_mode=True
)
result = get_redis_connection(redis_url=redis_url, redis_sentinels=None, async_mode=True)
assert result == mock_redis
mock_from_url.assert_called_once_with(redis_url, decode_responses=True)
@@ -226,7 +218,7 @@ class TestSentinelRedisProxy:
class TestSentinelRedisProxyCommands:
"""Test Redis commands through SentinelRedisProxy"""
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_hash_commands_sync(self, mock_sentinel_class):
"""Test Redis hash commands in sync mode"""
mock_sentinel = Mock()
@@ -234,39 +226,39 @@ class TestSentinelRedisProxyCommands:
# Mock hash command responses
mock_master.hset.return_value = 1
mock_master.hget.return_value = "test_value"
mock_master.hgetall.return_value = {"key1": "value1", "key2": "value2"}
mock_master.hget.return_value = 'test_value'
mock_master.hgetall.return_value = {'key1': 'value1', 'key2': 'value2'}
mock_master.hdel.return_value = 1
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test hset
hset_method = proxy.__getattr__("hset")
result = hset_method("test_hash", "field1", "value1")
hset_method = proxy.__getattr__('hset')
result = hset_method('test_hash', 'field1', 'value1')
assert result == 1
mock_master.hset.assert_called_with("test_hash", "field1", "value1")
mock_master.hset.assert_called_with('test_hash', 'field1', 'value1')
# Test hget
hget_method = proxy.__getattr__("hget")
result = hget_method("test_hash", "field1")
assert result == "test_value"
mock_master.hget.assert_called_with("test_hash", "field1")
hget_method = proxy.__getattr__('hget')
result = hget_method('test_hash', 'field1')
assert result == 'test_value'
mock_master.hget.assert_called_with('test_hash', 'field1')
# Test hgetall
hgetall_method = proxy.__getattr__("hgetall")
result = hgetall_method("test_hash")
assert result == {"key1": "value1", "key2": "value2"}
mock_master.hgetall.assert_called_with("test_hash")
hgetall_method = proxy.__getattr__('hgetall')
result = hgetall_method('test_hash')
assert result == {'key1': 'value1', 'key2': 'value2'}
mock_master.hgetall.assert_called_with('test_hash')
# Test hdel
hdel_method = proxy.__getattr__("hdel")
result = hdel_method("test_hash", "field1")
hdel_method = proxy.__getattr__('hdel')
result = hdel_method('test_hash', 'field1')
assert result == 1
mock_master.hdel.assert_called_with("test_hash", "field1")
mock_master.hdel.assert_called_with('test_hash', 'field1')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_hash_commands_async(self, mock_sentinel_class):
"""Test Redis hash commands in async mode"""
@@ -275,34 +267,32 @@ class TestSentinelRedisProxyCommands:
# Mock async hash command responses
mock_master.hset = AsyncMock(return_value=1)
mock_master.hget = AsyncMock(return_value="test_value")
mock_master.hgetall = AsyncMock(
return_value={"key1": "value1", "key2": "value2"}
)
mock_master.hget = AsyncMock(return_value='test_value')
mock_master.hgetall = AsyncMock(return_value={'key1': 'value1', 'key2': 'value2'})
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test hset
hset_method = proxy.__getattr__("hset")
result = await hset_method("test_hash", "field1", "value1")
hset_method = proxy.__getattr__('hset')
result = await hset_method('test_hash', 'field1', 'value1')
assert result == 1
mock_master.hset.assert_called_with("test_hash", "field1", "value1")
mock_master.hset.assert_called_with('test_hash', 'field1', 'value1')
# Test hget
hget_method = proxy.__getattr__("hget")
result = await hget_method("test_hash", "field1")
assert result == "test_value"
mock_master.hget.assert_called_with("test_hash", "field1")
hget_method = proxy.__getattr__('hget')
result = await hget_method('test_hash', 'field1')
assert result == 'test_value'
mock_master.hget.assert_called_with('test_hash', 'field1')
# Test hgetall
hgetall_method = proxy.__getattr__("hgetall")
result = await hgetall_method("test_hash")
assert result == {"key1": "value1", "key2": "value2"}
mock_master.hgetall.assert_called_with("test_hash")
hgetall_method = proxy.__getattr__('hgetall')
result = await hgetall_method('test_hash')
assert result == {'key1': 'value1', 'key2': 'value2'}
mock_master.hgetall.assert_called_with('test_hash')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_string_commands_sync(self, mock_sentinel_class):
"""Test Redis string commands in sync mode"""
mock_sentinel = Mock()
@@ -310,39 +300,39 @@ class TestSentinelRedisProxyCommands:
# Mock string command responses
mock_master.set.return_value = True
mock_master.get.return_value = "test_value"
mock_master.get.return_value = 'test_value'
mock_master.delete.return_value = 1
mock_master.exists.return_value = True
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test set
set_method = proxy.__getattr__("set")
result = set_method("test_key", "test_value")
set_method = proxy.__getattr__('set')
result = set_method('test_key', 'test_value')
assert result is True
mock_master.set.assert_called_with("test_key", "test_value")
mock_master.set.assert_called_with('test_key', 'test_value')
# Test get
get_method = proxy.__getattr__("get")
result = get_method("test_key")
assert result == "test_value"
mock_master.get.assert_called_with("test_key")
get_method = proxy.__getattr__('get')
result = get_method('test_key')
assert result == 'test_value'
mock_master.get.assert_called_with('test_key')
# Test delete
delete_method = proxy.__getattr__("delete")
result = delete_method("test_key")
delete_method = proxy.__getattr__('delete')
result = delete_method('test_key')
assert result == 1
mock_master.delete.assert_called_with("test_key")
mock_master.delete.assert_called_with('test_key')
# Test exists
exists_method = proxy.__getattr__("exists")
result = exists_method("test_key")
exists_method = proxy.__getattr__('exists')
result = exists_method('test_key')
assert result is True
mock_master.exists.assert_called_with("test_key")
mock_master.exists.assert_called_with('test_key')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_list_commands_sync(self, mock_sentinel_class):
"""Test Redis list commands in sync mode"""
mock_sentinel = Mock()
@@ -350,39 +340,39 @@ class TestSentinelRedisProxyCommands:
# Mock list command responses
mock_master.lpush.return_value = 1
mock_master.rpop.return_value = "test_value"
mock_master.rpop.return_value = 'test_value'
mock_master.llen.return_value = 5
mock_master.lrange.return_value = ["item1", "item2", "item3"]
mock_master.lrange.return_value = ['item1', 'item2', 'item3']
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test lpush
lpush_method = proxy.__getattr__("lpush")
result = lpush_method("test_list", "item1")
lpush_method = proxy.__getattr__('lpush')
result = lpush_method('test_list', 'item1')
assert result == 1
mock_master.lpush.assert_called_with("test_list", "item1")
mock_master.lpush.assert_called_with('test_list', 'item1')
# Test rpop
rpop_method = proxy.__getattr__("rpop")
result = rpop_method("test_list")
assert result == "test_value"
mock_master.rpop.assert_called_with("test_list")
rpop_method = proxy.__getattr__('rpop')
result = rpop_method('test_list')
assert result == 'test_value'
mock_master.rpop.assert_called_with('test_list')
# Test llen
llen_method = proxy.__getattr__("llen")
result = llen_method("test_list")
llen_method = proxy.__getattr__('llen')
result = llen_method('test_list')
assert result == 5
mock_master.llen.assert_called_with("test_list")
mock_master.llen.assert_called_with('test_list')
# Test lrange
lrange_method = proxy.__getattr__("lrange")
result = lrange_method("test_list", 0, -1)
assert result == ["item1", "item2", "item3"]
mock_master.lrange.assert_called_with("test_list", 0, -1)
lrange_method = proxy.__getattr__('lrange')
result = lrange_method('test_list', 0, -1)
assert result == ['item1', 'item2', 'item3']
mock_master.lrange.assert_called_with('test_list', 0, -1)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_pubsub_commands_sync(self, mock_sentinel_class):
"""Test Redis pubsub commands in sync mode"""
mock_sentinel = Mock()
@@ -393,25 +383,25 @@ class TestSentinelRedisProxyCommands:
mock_master.pubsub.return_value = mock_pubsub
mock_master.publish.return_value = 1
mock_pubsub.subscribe.return_value = None
mock_pubsub.get_message.return_value = {"type": "message", "data": "test_data"}
mock_pubsub.get_message.return_value = {'type': 'message', 'data': 'test_data'}
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test pubsub (factory method - should pass through)
pubsub_method = proxy.__getattr__("pubsub")
pubsub_method = proxy.__getattr__('pubsub')
result = pubsub_method()
assert result == mock_pubsub
mock_master.pubsub.assert_called_once()
# Test publish
publish_method = proxy.__getattr__("publish")
result = publish_method("test_channel", "test_message")
publish_method = proxy.__getattr__('publish')
result = publish_method('test_channel', 'test_message')
assert result == 1
mock_master.publish.assert_called_with("test_channel", "test_message")
mock_master.publish.assert_called_with('test_channel', 'test_message')
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_pipeline_commands_sync(self, mock_sentinel_class):
"""Test Redis pipeline commands in sync mode"""
mock_sentinel = Mock()
@@ -422,19 +412,19 @@ class TestSentinelRedisProxyCommands:
mock_master.pipeline.return_value = mock_pipeline
mock_pipeline.set.return_value = mock_pipeline
mock_pipeline.get.return_value = mock_pipeline
mock_pipeline.execute.return_value = [True, "test_value"]
mock_pipeline.execute.return_value = [True, 'test_value']
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test pipeline (factory method - should pass through)
pipeline_method = proxy.__getattr__("pipeline")
pipeline_method = proxy.__getattr__('pipeline')
result = pipeline_method()
assert result == mock_pipeline
mock_master.pipeline.assert_called_once()
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_commands_with_failover_retry(self, mock_sentinel_class):
"""Test Redis commands with failover retry mechanism"""
mock_sentinel = Mock()
@@ -442,27 +432,27 @@ class TestSentinelRedisProxyCommands:
# First call fails with connection error, second succeeds
mock_master.hget.side_effect = [
redis.exceptions.ConnectionError("Connection failed"),
"recovered_value",
redis.exceptions.ConnectionError('Connection failed'),
'recovered_value',
]
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test hget with retry
hget_method = proxy.__getattr__("hget")
result = hget_method("test_hash", "field1")
hget_method = proxy.__getattr__('hget')
result = hget_method('test_hash', 'field1')
assert result == "recovered_value"
assert result == 'recovered_value'
assert mock_master.hget.call_count == 2
# Verify both calls were made with same parameters
expected_calls = [(("test_hash", "field1"),), (("test_hash", "field1"),)]
expected_calls = [(('test_hash', 'field1'),), (('test_hash', 'field1'),)]
actual_calls = [call.args for call in mock_master.hget.call_args_list]
assert actual_calls == expected_calls
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
def test_commands_with_readonly_error_retry(self, mock_sentinel_class):
"""Test Redis commands with ReadOnlyError retry mechanism"""
mock_sentinel = Mock()
@@ -470,32 +460,30 @@ class TestSentinelRedisProxyCommands:
# First call fails with ReadOnlyError, second succeeds
mock_master.hset.side_effect = [
redis.exceptions.ReadOnlyError(
"READONLY You can't write against a read only replica"
),
redis.exceptions.ReadOnlyError("READONLY You can't write against a read only replica"),
1,
]
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=False)
# Test hset with retry
hset_method = proxy.__getattr__("hset")
result = hset_method("test_hash", "field1", "value1")
hset_method = proxy.__getattr__('hset')
result = hset_method('test_hash', 'field1', 'value1')
assert result == 1
assert mock_master.hset.call_count == 2
# Verify both calls were made with same parameters
expected_calls = [
(("test_hash", "field1", "value1"),),
(("test_hash", "field1", "value1"),),
(('test_hash', 'field1', 'value1'),),
(('test_hash', 'field1', 'value1'),),
]
actual_calls = [call.args for call in mock_master.hset.call_args_list]
assert actual_calls == expected_calls
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_async_commands_with_failover_retry(self, mock_sentinel_class):
"""Test async Redis commands with failover retry mechanism"""
@@ -505,24 +493,24 @@ class TestSentinelRedisProxyCommands:
# First call fails with connection error, second succeeds
mock_master.hget = AsyncMock(
side_effect=[
redis.exceptions.ConnectionError("Connection failed"),
"recovered_value",
redis.exceptions.ConnectionError('Connection failed'),
'recovered_value',
]
)
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test async hget with retry
hget_method = proxy.__getattr__("hget")
result = await hget_method("test_hash", "field1")
hget_method = proxy.__getattr__('hget')
result = await hget_method('test_hash', 'field1')
assert result == "recovered_value"
assert result == 'recovered_value'
assert mock_master.hget.call_count == 2
# Verify both calls were made with same parameters
expected_calls = [(("test_hash", "field1"),), (("test_hash", "field1"),)]
expected_calls = [(('test_hash', 'field1'),), (('test_hash', 'field1'),)]
actual_calls = [call.args for call in mock_master.hget.call_args_list]
assert actual_calls == expected_calls
@@ -530,7 +518,7 @@ class TestSentinelRedisProxyCommands:
class TestSentinelRedisProxyFactoryMethods:
"""Test Redis factory methods in async mode - these are special cases that remain sync"""
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_pubsub_factory_method_async(self, mock_sentinel_class):
"""Test pubsub factory method in async mode - should pass through without wrapping"""
@@ -542,10 +530,10 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.pubsub.return_value = mock_pubsub
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test pubsub factory method - should NOT be wrapped as async
pubsub_method = proxy.__getattr__("pubsub")
pubsub_method = proxy.__getattr__('pubsub')
result = pubsub_method()
assert result == mock_pubsub
@@ -554,7 +542,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Verify it's not wrapped as async (no await needed)
assert not inspect.iscoroutine(result)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_pipeline_factory_method_async(self, mock_sentinel_class):
"""Test pipeline factory method in async mode - should pass through without wrapping"""
@@ -566,14 +554,14 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.pipeline.return_value = mock_pipeline
mock_pipeline.set.return_value = mock_pipeline
mock_pipeline.get.return_value = mock_pipeline
mock_pipeline.execute.return_value = [True, "test_value"]
mock_pipeline.execute.return_value = [True, 'test_value']
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test pipeline factory method - should NOT be wrapped as async
pipeline_method = proxy.__getattr__("pipeline")
pipeline_method = proxy.__getattr__('pipeline')
result = pipeline_method()
assert result == mock_pipeline
@@ -583,10 +571,10 @@ class TestSentinelRedisProxyFactoryMethods:
assert not inspect.iscoroutine(result)
# Test pipeline usage (these should also be sync)
pipeline_result = result.set("key", "value").get("key").execute()
assert pipeline_result == [True, "test_value"]
pipeline_result = result.set('key', 'value').get('key').execute()
assert pipeline_result == [True, 'test_value']
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_factory_methods_vs_regular_commands_async(self, mock_sentinel_class):
"""Test that factory methods behave differently from regular commands in async mode"""
@@ -596,19 +584,19 @@ class TestSentinelRedisProxyFactoryMethods:
# Mock both factory method and regular command
mock_pubsub = Mock()
mock_master.pubsub.return_value = mock_pubsub
mock_master.get = AsyncMock(return_value="test_value")
mock_master.get = AsyncMock(return_value='test_value')
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test factory method - should NOT be wrapped
pubsub_method = proxy.__getattr__("pubsub")
pubsub_method = proxy.__getattr__('pubsub')
pubsub_result = pubsub_method()
# Test regular command - should be wrapped as async
get_method = proxy.__getattr__("get")
get_result = get_method("test_key")
get_method = proxy.__getattr__('get')
get_result = get_method('test_key')
# Factory method returns directly
assert pubsub_result == mock_pubsub
@@ -619,9 +607,9 @@ class TestSentinelRedisProxyFactoryMethods:
# Regular command needs await
actual_value = await get_result
assert actual_value == "test_value"
assert actual_value == 'test_value'
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_factory_methods_with_failover_async(self, mock_sentinel_class):
"""Test factory methods with failover in async mode"""
@@ -631,16 +619,16 @@ class TestSentinelRedisProxyFactoryMethods:
# First call fails, second succeeds
mock_pubsub = Mock()
mock_master.pubsub.side_effect = [
redis.exceptions.ConnectionError("Connection failed"),
redis.exceptions.ConnectionError('Connection failed'),
mock_pubsub,
]
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test pubsub factory method with failover
pubsub_method = proxy.__getattr__("pubsub")
pubsub_method = proxy.__getattr__('pubsub')
result = pubsub_method()
assert result == mock_pubsub
@@ -649,7 +637,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Verify it's still not wrapped as async after retry
assert not inspect.iscoroutine(result)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_monitor_factory_method_async(self, mock_sentinel_class):
"""Test monitor factory method in async mode - should pass through without wrapping"""
@@ -661,10 +649,10 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.monitor.return_value = mock_monitor
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test monitor factory method - should NOT be wrapped as async
monitor_method = proxy.__getattr__("monitor")
monitor_method = proxy.__getattr__('monitor')
result = monitor_method()
assert result == mock_monitor
@@ -673,7 +661,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Verify it's not wrapped as async (no await needed)
assert not inspect.iscoroutine(result)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_client_factory_method_async(self, mock_sentinel_class):
"""Test client factory method in async mode - should pass through without wrapping"""
@@ -685,10 +673,10 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.client.return_value = mock_client
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test client factory method - should NOT be wrapped as async
client_method = proxy.__getattr__("client")
client_method = proxy.__getattr__('client')
result = client_method()
assert result == mock_client
@@ -697,7 +685,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Verify it's not wrapped as async (no await needed)
assert not inspect.iscoroutine(result)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_transaction_factory_method_async(self, mock_sentinel_class):
"""Test transaction factory method in async mode - should pass through without wrapping"""
@@ -709,10 +697,10 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.transaction.return_value = mock_transaction
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test transaction factory method - should NOT be wrapped as async
transaction_method = proxy.__getattr__("transaction")
transaction_method = proxy.__getattr__('transaction')
result = transaction_method()
assert result == mock_transaction
@@ -721,7 +709,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Verify it's not wrapped as async (no await needed)
assert not inspect.iscoroutine(result)
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_all_factory_methods_async(self, mock_sentinel_class):
"""Test all factory methods in async mode - comprehensive test"""
@@ -730,11 +718,11 @@ class TestSentinelRedisProxyFactoryMethods:
# Mock all factory methods
mock_objects = {
"pipeline": Mock(),
"pubsub": Mock(),
"monitor": Mock(),
"client": Mock(),
"transaction": Mock(),
'pipeline': Mock(),
'pubsub': Mock(),
'monitor': Mock(),
'client': Mock(),
'transaction': Mock(),
}
for method_name, mock_obj in mock_objects.items():
@@ -742,7 +730,7 @@ class TestSentinelRedisProxyFactoryMethods:
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Test all factory methods
for method_name, expected_obj in mock_objects.items():
@@ -756,7 +744,7 @@ class TestSentinelRedisProxyFactoryMethods:
# Reset mock for next iteration
getattr(mock_master, method_name).reset_mock()
@patch("redis.sentinel.Sentinel")
@patch('redis.sentinel.Sentinel')
@pytest.mark.asyncio
async def test_mixed_factory_and_regular_commands_async(self, mock_sentinel_class):
"""Test using both factory methods and regular commands in async mode"""
@@ -768,26 +756,26 @@ class TestSentinelRedisProxyFactoryMethods:
mock_master.pipeline.return_value = mock_pipeline
mock_pipeline.set.return_value = mock_pipeline
mock_pipeline.get.return_value = mock_pipeline
mock_pipeline.execute.return_value = [True, "pipeline_value"]
mock_pipeline.execute.return_value = [True, 'pipeline_value']
mock_master.get = AsyncMock(return_value="regular_value")
mock_master.get = AsyncMock(return_value='regular_value')
mock_sentinel.master_for.return_value = mock_master
proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True)
proxy = SentinelRedisProxy(mock_sentinel, 'mymaster', async_mode=True)
# Use factory method (sync)
pipeline = proxy.__getattr__("pipeline")()
pipeline_result = pipeline.set("key1", "value1").get("key1").execute()
pipeline = proxy.__getattr__('pipeline')()
pipeline_result = pipeline.set('key1', 'value1').get('key1').execute()
# Use regular command (async)
get_method = proxy.__getattr__("get")
regular_result = await get_method("key2")
get_method = proxy.__getattr__('get')
regular_result = await get_method('key2')
# Verify both work correctly
assert pipeline_result == [True, "pipeline_value"]
assert regular_result == "regular_value"
assert pipeline_result == [True, 'pipeline_value']
assert regular_result == 'regular_value'
# Verify calls
mock_master.pipeline.assert_called_once()
mock_master.get.assert_called_with("key2")
mock_master.get.assert_called_with('key2')