| | import pytest |
| | from unittest.mock import Mock, patch, AsyncMock |
| | import redis |
| | from open_webui.utils.redis import ( |
| | SentinelRedisProxy, |
| | parse_redis_service_url, |
| | get_redis_connection, |
| | get_sentinels_from_env, |
| | MAX_RETRY_COUNT, |
| | ) |
| | import inspect |
| |
|
| |
|
| | class TestSentinelRedisProxy: |
| | """Test Redis Sentinel failover functionality""" |
| |
|
| | def test_parse_redis_service_url_valid(self): |
| | """Test parsing valid Redis service URL""" |
| | 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 |
| |
|
| | def test_parse_redis_service_url_defaults(self): |
| | """Test parsing Redis service URL with defaults""" |
| | 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 |
| |
|
| | 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") |
| |
|
| | def test_get_sentinels_from_env(self): |
| | """Test parsing sentinel hosts from environment""" |
| | hosts = "sentinel1,sentinel2,sentinel3" |
| | port = "26379" |
| |
|
| | result = get_sentinels_from_env(hosts, port) |
| | 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") |
| | assert result == [] |
| |
|
| | @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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | 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") |
| |
|
| | @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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | 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") |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_sentinel_redis_proxy_failover_retry(self, mock_sentinel_class): |
| | """Test retry mechanism during failover""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.get.side_effect = [ |
| | redis.exceptions.ConnectionError("Master down"), |
| | "test_value", |
| | ] |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | get_method = proxy.__getattr__("get") |
| | result = get_method("test_key") |
| |
|
| | assert result == "test_value" |
| | assert mock_master.get.call_count == 2 |
| |
|
| | @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() |
| |
|
| | |
| | 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) |
| |
|
| | get_method = proxy.__getattr__("get") |
| |
|
| | with pytest.raises(redis.exceptions.ConnectionError): |
| | get_method("test_key") |
| |
|
| | assert mock_master.get.call_count == MAX_RETRY_COUNT |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_sentinel_redis_proxy_readonly_error_retry(self, mock_sentinel_class): |
| | """Test retry on ReadOnlyError""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.get.side_effect = [ |
| | redis.exceptions.ReadOnlyError("Read only"), |
| | "test_value", |
| | ] |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | get_method = proxy.__getattr__("get") |
| | result = get_method("test_key") |
| |
|
| | assert result == "test_value" |
| | assert mock_master.get.call_count == 2 |
| |
|
| | @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() |
| | mock_master = Mock() |
| | mock_pipeline = Mock() |
| | mock_master.pipeline.return_value = mock_pipeline |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | 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 |
| | ): |
| | """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" |
| |
|
| | 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") |
| | 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" |
| |
|
| | 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") |
| | 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" |
| |
|
| | 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) |
| |
|
| |
|
| | class TestSentinelRedisProxyCommands: |
| | """Test Redis commands through SentinelRedisProxy""" |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_hash_commands_sync(self, mock_sentinel_class): |
| | """Test Redis hash commands in sync mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.hset.return_value = 1 |
| | 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) |
| |
|
| | |
| | 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") |
| |
|
| | |
| | hget_method = proxy.__getattr__("hget") |
| | result = hget_method("test_hash", "field1") |
| | assert result == "test_value" |
| | mock_master.hget.assert_called_with("test_hash", "field1") |
| |
|
| | |
| | hgetall_method = proxy.__getattr__("hgetall") |
| | result = hgetall_method("test_hash") |
| | assert result == {"key1": "value1", "key2": "value2"} |
| | mock_master.hgetall.assert_called_with("test_hash") |
| |
|
| | |
| | hdel_method = proxy.__getattr__("hdel") |
| | result = hdel_method("test_hash", "field1") |
| | assert result == 1 |
| | mock_master.hdel.assert_called_with("test_hash", "field1") |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | @pytest.mark.asyncio |
| | async def test_hash_commands_async(self, mock_sentinel_class): |
| | """Test Redis hash commands in async mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | 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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | 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") |
| |
|
| | |
| | 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") |
| |
|
| | |
| | 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") |
| | def test_string_commands_sync(self, mock_sentinel_class): |
| | """Test Redis string commands in sync mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.set.return_value = True |
| | 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) |
| |
|
| | |
| | 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") |
| |
|
| | |
| | get_method = proxy.__getattr__("get") |
| | result = get_method("test_key") |
| | assert result == "test_value" |
| | mock_master.get.assert_called_with("test_key") |
| |
|
| | |
| | delete_method = proxy.__getattr__("delete") |
| | result = delete_method("test_key") |
| | assert result == 1 |
| | mock_master.delete.assert_called_with("test_key") |
| |
|
| | |
| | exists_method = proxy.__getattr__("exists") |
| | result = exists_method("test_key") |
| | assert result is True |
| | mock_master.exists.assert_called_with("test_key") |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_list_commands_sync(self, mock_sentinel_class): |
| | """Test Redis list commands in sync mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.lpush.return_value = 1 |
| | mock_master.rpop.return_value = "test_value" |
| | mock_master.llen.return_value = 5 |
| | mock_master.lrange.return_value = ["item1", "item2", "item3"] |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | lpush_method = proxy.__getattr__("lpush") |
| | result = lpush_method("test_list", "item1") |
| | assert result == 1 |
| | mock_master.lpush.assert_called_with("test_list", "item1") |
| |
|
| | |
| | rpop_method = proxy.__getattr__("rpop") |
| | result = rpop_method("test_list") |
| | assert result == "test_value" |
| | mock_master.rpop.assert_called_with("test_list") |
| |
|
| | |
| | llen_method = proxy.__getattr__("llen") |
| | result = llen_method("test_list") |
| | assert result == 5 |
| | mock_master.llen.assert_called_with("test_list") |
| |
|
| | |
| | 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") |
| | def test_pubsub_commands_sync(self, mock_sentinel_class): |
| | """Test Redis pubsub commands in sync mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_pubsub = Mock() |
| |
|
| | |
| | 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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | pubsub_method = proxy.__getattr__("pubsub") |
| | result = pubsub_method() |
| | assert result == mock_pubsub |
| | mock_master.pubsub.assert_called_once() |
| |
|
| | |
| | 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") |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_pipeline_commands_sync(self, mock_sentinel_class): |
| | """Test Redis pipeline commands in sync mode""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_pipeline = Mock() |
| |
|
| | |
| | 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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | pipeline_method = proxy.__getattr__("pipeline") |
| | result = pipeline_method() |
| | assert result == mock_pipeline |
| | mock_master.pipeline.assert_called_once() |
| |
|
| | @patch("redis.sentinel.Sentinel") |
| | def test_commands_with_failover_retry(self, mock_sentinel_class): |
| | """Test Redis commands with failover retry mechanism""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.hget.side_effect = [ |
| | redis.exceptions.ConnectionError("Connection failed"), |
| | "recovered_value", |
| | ] |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) |
| |
|
| | |
| | hget_method = proxy.__getattr__("hget") |
| | result = hget_method("test_hash", "field1") |
| |
|
| | assert result == "recovered_value" |
| | assert mock_master.hget.call_count == 2 |
| |
|
| | |
| | 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") |
| | def test_commands_with_readonly_error_retry(self, mock_sentinel_class): |
| | """Test Redis commands with ReadOnlyError retry mechanism""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.hset.side_effect = [ |
| | 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) |
| |
|
| | |
| | hset_method = proxy.__getattr__("hset") |
| | result = hset_method("test_hash", "field1", "value1") |
| |
|
| | assert result == 1 |
| | assert mock_master.hset.call_count == 2 |
| |
|
| | |
| | expected_calls = [ |
| | (("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") |
| | @pytest.mark.asyncio |
| | async def test_async_commands_with_failover_retry(self, mock_sentinel_class): |
| | """Test async Redis commands with failover retry mechanism""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_master.hget = AsyncMock( |
| | side_effect=[ |
| | redis.exceptions.ConnectionError("Connection failed"), |
| | "recovered_value", |
| | ] |
| | ) |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | hget_method = proxy.__getattr__("hget") |
| | result = await hget_method("test_hash", "field1") |
| |
|
| | assert result == "recovered_value" |
| | assert mock_master.hget.call_count == 2 |
| |
|
| | |
| | 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 |
| |
|
| |
|
| | class TestSentinelRedisProxyFactoryMethods: |
| | """Test Redis factory methods in async mode - these are special cases that remain sync""" |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_pubsub = Mock() |
| |
|
| | |
| | mock_master.pubsub.return_value = mock_pubsub |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | pubsub_method = proxy.__getattr__("pubsub") |
| | result = pubsub_method() |
| |
|
| | assert result == mock_pubsub |
| | mock_master.pubsub.assert_called_once() |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_pipeline = Mock() |
| |
|
| | |
| | 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_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | pipeline_method = proxy.__getattr__("pipeline") |
| | result = pipeline_method() |
| |
|
| | assert result == mock_pipeline |
| | mock_master.pipeline.assert_called_once() |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | |
| | pipeline_result = result.set("key", "value").get("key").execute() |
| | assert pipeline_result == [True, "test_value"] |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_pubsub = Mock() |
| | mock_master.pubsub.return_value = mock_pubsub |
| | mock_master.get = AsyncMock(return_value="test_value") |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | pubsub_method = proxy.__getattr__("pubsub") |
| | pubsub_result = pubsub_method() |
| |
|
| | |
| | get_method = proxy.__getattr__("get") |
| | get_result = get_method("test_key") |
| |
|
| | |
| | assert pubsub_result == mock_pubsub |
| | assert not inspect.iscoroutine(pubsub_result) |
| |
|
| | |
| | assert inspect.iscoroutine(get_result) |
| |
|
| | |
| | actual_value = await get_result |
| | assert actual_value == "test_value" |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_pubsub = Mock() |
| | mock_master.pubsub.side_effect = [ |
| | redis.exceptions.ConnectionError("Connection failed"), |
| | mock_pubsub, |
| | ] |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | pubsub_method = proxy.__getattr__("pubsub") |
| | result = pubsub_method() |
| |
|
| | assert result == mock_pubsub |
| | assert mock_master.pubsub.call_count == 2 |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_monitor = Mock() |
| |
|
| | |
| | mock_master.monitor.return_value = mock_monitor |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | monitor_method = proxy.__getattr__("monitor") |
| | result = monitor_method() |
| |
|
| | assert result == mock_monitor |
| | mock_master.monitor.assert_called_once() |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_client = Mock() |
| |
|
| | |
| | mock_master.client.return_value = mock_client |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | client_method = proxy.__getattr__("client") |
| | result = client_method() |
| |
|
| | assert result == mock_client |
| | mock_master.client.assert_called_once() |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| | mock_transaction = Mock() |
| |
|
| | |
| | mock_master.transaction.return_value = mock_transaction |
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | transaction_method = proxy.__getattr__("transaction") |
| | result = transaction_method() |
| |
|
| | assert result == mock_transaction |
| | mock_master.transaction.assert_called_once() |
| |
|
| | |
| | assert not inspect.iscoroutine(result) |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_objects = { |
| | "pipeline": Mock(), |
| | "pubsub": Mock(), |
| | "monitor": Mock(), |
| | "client": Mock(), |
| | "transaction": Mock(), |
| | } |
| |
|
| | for method_name, mock_obj in mock_objects.items(): |
| | setattr(mock_master, method_name, Mock(return_value=mock_obj)) |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | for method_name, expected_obj in mock_objects.items(): |
| | method = proxy.__getattr__(method_name) |
| | result = method() |
| |
|
| | assert result == expected_obj |
| | assert not inspect.iscoroutine(result) |
| | getattr(mock_master, method_name).assert_called_once() |
| |
|
| | |
| | getattr(mock_master, method_name).reset_mock() |
| |
|
| | @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""" |
| | mock_sentinel = Mock() |
| | mock_master = Mock() |
| |
|
| | |
| | mock_pipeline = Mock() |
| | 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_master.get = AsyncMock(return_value="regular_value") |
| |
|
| | mock_sentinel.master_for.return_value = mock_master |
| |
|
| | proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) |
| |
|
| | |
| | pipeline = proxy.__getattr__("pipeline")() |
| | pipeline_result = pipeline.set("key1", "value1").get("key1").execute() |
| |
|
| | |
| | get_method = proxy.__getattr__("get") |
| | regular_result = await get_method("key2") |
| |
|
| | |
| | assert pipeline_result == [True, "pipeline_value"] |
| | assert regular_result == "regular_value" |
| |
|
| | |
| | mock_master.pipeline.assert_called_once() |
| | mock_master.get.assert_called_with("key2") |
| |
|