| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  | 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, | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     MAX_RETRY_COUNT, | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  | import inspect | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 == [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test attribute access | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     @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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test async attribute access | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     def test_sentinel_redis_proxy_failover_retry(self, mock_sentinel_class): | 
					
						
							|  |  |  |         """Test retry mechanism during failover""" | 
					
						
							|  |  |  |         mock_sentinel = Mock() | 
					
						
							|  |  |  |         mock_master = Mock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First call fails, second succeeds | 
					
						
							|  |  |  |         mock_master.get.side_effect = [ | 
					
						
							|  |  |  |             redis.exceptions.ConnectionError("Master down"), | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |             "test_value", | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         ] | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     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_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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     def test_sentinel_redis_proxy_readonly_error_retry(self, mock_sentinel_class): | 
					
						
							|  |  |  |         """Test retry on ReadOnlyError""" | 
					
						
							|  |  |  |         mock_sentinel = Mock() | 
					
						
							|  |  |  |         mock_master = Mock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First call gets ReadOnlyError (old master), second succeeds (new master) | 
					
						
							|  |  |  |         mock_master.get.side_effect = [ | 
					
						
							|  |  |  |             redis.exceptions.ReadOnlyError("Read only"), | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |             "test_value", | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         ] | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Factory methods should be passed through without wrapping | 
					
						
							|  |  |  |         pipeline_method = proxy.__getattr__("pipeline") | 
					
						
							|  |  |  |         result = pipeline_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_pipeline | 
					
						
							|  |  |  |         mock_master.pipeline.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.sentinel.Sentinel") | 
					
						
							|  |  |  |     @patch("redis.from_url") | 
					
						
							|  |  |  |     def test_get_redis_connection_with_sentinel( | 
					
						
							|  |  |  |         self, mock_from_url, mock_sentinel_class | 
					
						
							|  |  |  |     ): | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         """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( | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |             redis_url=redis_url, redis_sentinels=sentinels, async_mode=False | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert isinstance(result, SentinelRedisProxy) | 
					
						
							|  |  |  |         mock_sentinel_class.assert_called_once() | 
					
						
							|  |  |  |         mock_from_url.assert_not_called() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.Redis.from_url") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     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( | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |             redis_url=redis_url, redis_sentinels=None, async_mode=False | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_redis | 
					
						
							|  |  |  |         mock_from_url.assert_called_once_with(redis_url, decode_responses=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |     @patch("redis.asyncio.from_url") | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |     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( | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |             redis_url=redis_url, redis_sentinels=None, async_mode=True | 
					
						
							| 
									
										
										
										
											2025-07-15 09:56:13 +08:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_redis | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  |         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 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.hdel.return_value = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test hset | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test hdel | 
					
						
							|  |  |  |         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 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_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test hset | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @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 string command responses | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test set | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test get | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  |         assert result == 1 | 
					
						
							|  |  |  |         mock_master.delete.assert_called_with("test_key") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test exists | 
					
						
							|  |  |  |         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 list command responses | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test lpush | 
					
						
							|  |  |  |         lpush_method = proxy.__getattr__("lpush") | 
					
						
							|  |  |  |         result = lpush_method("test_list", "item1") | 
					
						
							|  |  |  |         assert result == 1 | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test llen | 
					
						
							|  |  |  |         llen_method = proxy.__getattr__("llen") | 
					
						
							|  |  |  |         result = llen_method("test_list") | 
					
						
							|  |  |  |         assert result == 5 | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @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 pubsub responses | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test pubsub (factory method - should pass through) | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  |         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 pipeline responses | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test pipeline (factory method - should pass through) | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First call fails with connection error, second succeeds | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test hget with retry | 
					
						
							|  |  |  |         hget_method = proxy.__getattr__("hget") | 
					
						
							|  |  |  |         result = hget_method("test_hash", "field1") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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"),)] | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # 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" | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             1, | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test hset with retry | 
					
						
							|  |  |  |         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"),), | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First call fails with connection error, second succeeds | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test async hget with retry | 
					
						
							|  |  |  |         hget_method = proxy.__getattr__("hget") | 
					
						
							|  |  |  |         result = await hget_method("test_hash", "field1") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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"),)] | 
					
						
							|  |  |  |         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 pubsub factory method | 
					
						
							|  |  |  |         mock_master.pubsub.return_value = mock_pubsub | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test pubsub factory method - should NOT be wrapped as async | 
					
						
							|  |  |  |         pubsub_method = proxy.__getattr__("pubsub") | 
					
						
							|  |  |  |         result = pubsub_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_pubsub | 
					
						
							|  |  |  |         mock_master.pubsub.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's not wrapped as async (no await needed) | 
					
						
							|  |  |  |         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 pipeline factory method | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test pipeline factory method - should NOT be wrapped as async | 
					
						
							|  |  |  |         pipeline_method = proxy.__getattr__("pipeline") | 
					
						
							|  |  |  |         result = pipeline_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_pipeline | 
					
						
							|  |  |  |         mock_master.pipeline.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's not wrapped as async (no await needed) | 
					
						
							|  |  |  |         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"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @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 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_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test factory method - should NOT be wrapped | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Factory method returns directly | 
					
						
							|  |  |  |         assert pubsub_result == mock_pubsub | 
					
						
							|  |  |  |         assert not inspect.iscoroutine(pubsub_result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Regular command returns coroutine | 
					
						
							|  |  |  |         assert inspect.iscoroutine(get_result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Regular command needs await | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # First call fails, second succeeds | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test pubsub factory method with failover | 
					
						
							|  |  |  |         pubsub_method = proxy.__getattr__("pubsub") | 
					
						
							|  |  |  |         result = pubsub_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_pubsub | 
					
						
							|  |  |  |         assert mock_master.pubsub.call_count == 2  # Retry happened | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's still not wrapped as async after retry | 
					
						
							|  |  |  |         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 monitor factory method | 
					
						
							|  |  |  |         mock_master.monitor.return_value = mock_monitor | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test monitor factory method - should NOT be wrapped as async | 
					
						
							|  |  |  |         monitor_method = proxy.__getattr__("monitor") | 
					
						
							|  |  |  |         result = monitor_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_monitor | 
					
						
							|  |  |  |         mock_master.monitor.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's not wrapped as async (no await needed) | 
					
						
							|  |  |  |         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 client factory method | 
					
						
							|  |  |  |         mock_master.client.return_value = mock_client | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test client factory method - should NOT be wrapped as async | 
					
						
							|  |  |  |         client_method = proxy.__getattr__("client") | 
					
						
							|  |  |  |         result = client_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_client | 
					
						
							|  |  |  |         mock_master.client.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's not wrapped as async (no await needed) | 
					
						
							|  |  |  |         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 transaction factory method | 
					
						
							|  |  |  |         mock_master.transaction.return_value = mock_transaction | 
					
						
							|  |  |  |         mock_sentinel.master_for.return_value = mock_master | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         proxy = SentinelRedisProxy(mock_sentinel, "mymaster", async_mode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test transaction factory method - should NOT be wrapped as async | 
					
						
							|  |  |  |         transaction_method = proxy.__getattr__("transaction") | 
					
						
							|  |  |  |         result = transaction_method() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         assert result == mock_transaction | 
					
						
							|  |  |  |         mock_master.transaction.assert_called_once() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify it's not wrapped as async (no await needed) | 
					
						
							|  |  |  |         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 all factory methods | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Test all factory methods | 
					
						
							|  |  |  |         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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Reset mock for next iteration | 
					
						
							|  |  |  |             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 factory and regular commands | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Use factory method (sync) | 
					
						
							|  |  |  |         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") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Verify both work correctly | 
					
						
							|  |  |  |         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") |