""" Тесты для полного покрытия services/redis.py """ import json import logging from unittest.mock import AsyncMock, Mock, patch import pytest import redis.asyncio as aioredis from redis.asyncio import Redis from services.redis import ( RedisService, close_redis, init_redis, redis, ) class TestRedisServiceInitialization: """Тесты инициализации Redis сервиса""" def test_redis_service_init_with_url(self): """Тест инициализации с URL""" service = RedisService("redis://localhost:6379") assert service._redis_url == "redis://localhost:6379" assert service._is_available is True def test_redis_service_init_without_aioredis(self): """Тест инициализации без aioredis""" with patch("services.redis.aioredis", None): service = RedisService() assert service._is_available is False def test_redis_service_default_url(self): """Тест инициализации с дефолтным URL""" service = RedisService() assert service._redis_url is not None def test_is_connected_property(self): """Тест свойства is_connected""" service = RedisService() assert service.is_connected is False service._client = Mock() service._is_available = True assert service.is_connected is True service._is_available = False assert service.is_connected is False class TestRedisConnectionManagement: """Тесты управления соединениями Redis""" @pytest.mark.asyncio async def test_connect_success(self): """Тест успешного подключения""" service = RedisService() with patch("services.redis.aioredis.from_url") as mock_from_url: mock_client = AsyncMock() mock_client.ping = AsyncMock(return_value=True) mock_from_url.return_value = mock_client await service.connect() assert service._client is not None assert service.is_connected is True @pytest.mark.asyncio async def test_connect_failure(self): """Тест неудачного подключения""" service = RedisService() with patch("services.redis.aioredis.from_url") as mock_from_url: mock_from_url.side_effect = Exception("Connection failed") await service.connect() assert service._client is None assert service.is_connected is False @pytest.mark.asyncio async def test_connect_without_aioredis(self): """Тест подключения без aioredis""" with patch("services.redis.aioredis", None): service = RedisService() await service.connect() assert service._client is None @pytest.mark.asyncio async def test_connect_existing_client(self): """Тест подключения с существующим клиентом""" service = RedisService() mock_existing_client = AsyncMock() service._client = mock_existing_client with patch("services.redis.aioredis.from_url") as mock_from_url: mock_client = AsyncMock() mock_client.ping = AsyncMock(return_value=True) mock_from_url.return_value = mock_client await service.connect() # Старый клиент должен быть закрыт mock_existing_client.close.assert_called_once() @pytest.mark.asyncio async def test_disconnect(self): """Тест отключения""" service = RedisService() mock_client = AsyncMock() service._client = mock_client await service.close() mock_client.close.assert_called_once() assert service._client is None @pytest.mark.asyncio async def test_disconnect_no_client(self): """Тест отключения без клиента""" service = RedisService() service._client = None # Не должно вызывать ошибку await service.close() class TestRedisCommandExecution: """Тесты выполнения команд Redis""" @pytest.mark.asyncio async def test_execute_success(self): """Тест успешного выполнения команды""" service = RedisService() service._client = AsyncMock() service._is_available = True mock_method = AsyncMock(return_value="test_result") service._client.test_command = mock_method result = await service.execute("test_command", "arg1", "arg2") assert result == "test_result" mock_method.assert_called_once_with("arg1", "arg2") @pytest.mark.asyncio async def test_execute_without_aioredis(self): """Тест выполнения команды без aioredis""" with patch("services.redis.aioredis", None): service = RedisService() result = await service.execute("test_command") assert result is None @pytest.mark.asyncio async def test_execute_not_connected(self): """Тест выполнения команды без подключения""" service = RedisService() service._is_available = True with patch.object(service, "connect") as mock_connect: mock_connect.return_value = None service._client = None result = await service.execute("test_command") assert result is None @pytest.mark.asyncio async def test_execute_unknown_command(self): """Тест выполнения неизвестной команды""" service = RedisService() service._client = AsyncMock() service._is_available = True # Убираем все методы из клиента service._client.__dict__.clear() result = await service.execute("unknown_command") assert result is None @pytest.mark.asyncio async def test_execute_connection_error(self): """Тест выполнения команды с ошибкой соединения""" service = RedisService() service._client = AsyncMock() service._is_available = True mock_method = AsyncMock(side_effect=ConnectionError("Connection lost")) service._client.test_command = mock_method with patch.object(service, "connect") as mock_connect: mock_connect.return_value = None result = await service.execute("test_command") assert result is None @pytest.mark.asyncio async def test_execute_retry_success(self): """Тест успешного повтора команды""" service = RedisService() service._client = AsyncMock() service._is_available = True # Первый вызов падает, второй успешен mock_method = AsyncMock(side_effect=[ConnectionError("Connection lost"), "success"]) service._client.test_command = mock_method with patch.object(service, 'connect') as mock_connect: mock_connect.return_value = True result = await service.execute("test_command") assert result == "success" @pytest.mark.asyncio async def test_execute_retry_failure(self): """Тест неудачного повтора команды""" service = RedisService() service._client = AsyncMock() service._is_available = True mock_method = AsyncMock(side_effect=ConnectionError("Connection lost")) service._client.test_command = mock_method with patch.object(service, "connect") as mock_connect: mock_connect.return_value = None result = await service.execute("test_command") assert result is None @pytest.mark.asyncio async def test_execute_general_exception(self): """Тест общего исключения при выполнении команды""" service = RedisService() service._client = AsyncMock() service._is_available = True mock_method = AsyncMock(side_effect=Exception("General error")) service._client.test_command = mock_method result = await service.execute("test_command") assert result is None class TestRedisBasicOperations: """Тесты базовых операций Redis""" @pytest.mark.asyncio async def test_get(self): """Тест операции GET""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.get = AsyncMock(return_value="test_value") result = await service.get("test_key") assert result == "test_value" @pytest.mark.asyncio async def test_set(self): """Тест операции SET""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.set = AsyncMock(return_value=True) result = await service.set("test_key", "test_value") assert result is True @pytest.mark.asyncio async def test_set_with_expiration(self): """Тест операции SET с истечением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.setex = AsyncMock(return_value=True) result = await service.set("test_key", "test_value", ex=3600) assert result is True @pytest.mark.asyncio async def test_delete(self): """Тест операции DELETE""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.delete = AsyncMock(return_value=2) result = await service.delete("key1", "key2") assert result == 2 @pytest.mark.asyncio async def test_delete_none_result(self): """Тест операции DELETE с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.delete = AsyncMock(return_value=None) result = await service.delete("key1") assert result == 0 @pytest.mark.asyncio async def test_exists(self): """Тест операции EXISTS""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.exists = AsyncMock(return_value=1) result = await service.exists("test_key") assert result is True @pytest.mark.asyncio async def test_exists_false(self): """Тест операции EXISTS для несуществующего ключа""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.exists = AsyncMock(return_value=0) result = await service.exists("test_key") assert result is False class TestRedisHashOperations: """Тесты операций с хешами""" @pytest.mark.asyncio async def test_hset(self): """Тест операции HSET""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.hset = AsyncMock(return_value=1) await service.hset("test_hash", "field", "value") service._client.hset.assert_called_once_with("test_hash", "field", "value") @pytest.mark.asyncio async def test_hget(self): """Тест операции HGET""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.hget = AsyncMock(return_value="test_value") result = await service.hget("test_hash", "field") assert result == "test_value" @pytest.mark.asyncio async def test_hgetall(self): """Тест операции HGETALL""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.hgetall = AsyncMock(return_value={"field1": "value1", "field2": "value2"}) result = await service.hgetall("test_hash") assert result == {"field1": "value1", "field2": "value2"} @pytest.mark.asyncio async def test_hgetall_none_result(self): """Тест операции HGETALL с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.hgetall = AsyncMock(return_value=None) result = await service.hgetall("test_hash") assert result == {} class TestRedisSetOperations: """Тесты операций с множествами""" @pytest.mark.asyncio async def test_smembers(self): """Тест операции SMEMBERS""" service = RedisService() service._client = AsyncMock() service._is_available = True # Симулируем байтовые данные service._client.smembers = AsyncMock(return_value=[b"member1", b"member2"]) result = await service.smembers("test_set") assert result == {"member1", "member2"} @pytest.mark.asyncio async def test_smembers_string_data(self): """Тест операции SMEMBERS со строковыми данными""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.smembers = AsyncMock(return_value=["member1", "member2"]) result = await service.smembers("test_set") assert result == {"member1", "member2"} @pytest.mark.asyncio async def test_smembers_none_result(self): """Тест операции SMEMBERS с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.smembers = AsyncMock(return_value=None) result = await service.smembers("test_set") assert result == set() @pytest.mark.asyncio async def test_smembers_exception(self): """Тест операции SMEMBERS с исключением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.smembers = AsyncMock(side_effect=Exception("Redis error")) result = await service.smembers("test_set") assert result == set() @pytest.mark.asyncio async def test_smembers_not_connected(self): """Тест операции SMEMBERS без подключения""" service = RedisService() service._client = None service._is_available = True result = await service.smembers("test_set") assert result == set() @pytest.mark.asyncio async def test_sadd(self): """Тест операции SADD""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.sadd = AsyncMock(return_value=2) result = await service.sadd("test_set", "member1", "member2") assert result == 2 @pytest.mark.asyncio async def test_sadd_none_result(self): """Тест операции SADD с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.sadd = AsyncMock(return_value=None) result = await service.sadd("test_set", "member1") assert result == 0 @pytest.mark.asyncio async def test_srem(self): """Тест операции SREM""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.srem = AsyncMock(return_value=1) result = await service.srem("test_set", "member1") assert result == 1 @pytest.mark.asyncio async def test_srem_none_result(self): """Тест операции SREM с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.srem = AsyncMock(return_value=None) result = await service.srem("test_set", "member1") assert result == 0 class TestRedisUtilityOperations: """Тесты утилитарных операций Redis""" @pytest.mark.asyncio async def test_expire(self): """Тест операции EXPIRE""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.expire = AsyncMock(return_value=True) result = await service.expire("test_key", 3600) assert result is True @pytest.mark.asyncio async def test_expire_false(self): """Тест операции EXPIRE с False результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.expire = AsyncMock(return_value=False) result = await service.expire("test_key", 3600) assert result is False @pytest.mark.asyncio async def test_keys(self): """Тест операции KEYS""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.keys = AsyncMock(return_value=["key1", "key2"]) result = await service.keys("test:*") assert result == ["key1", "key2"] @pytest.mark.asyncio async def test_keys_none_result(self): """Тест операции KEYS с None результатом""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.keys = AsyncMock(return_value=None) result = await service.keys("test:*") assert result == [] @pytest.mark.asyncio async def test_ping(self): """Тест операции PING""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.ping = AsyncMock(return_value=True) result = await service.ping() assert result is True @pytest.mark.asyncio async def test_ping_not_connected(self): """Тест операции PING без подключения""" service = RedisService() service._client = None service._is_available = True result = await service.ping() assert result is False @pytest.mark.asyncio async def test_ping_exception(self): """Тест операции PING с исключением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.ping = AsyncMock(side_effect=Exception("Redis error")) result = await service.ping() assert result is False class TestRedisSerialization: """Тесты сериализации данных""" @pytest.mark.asyncio async def test_serialize_and_set_string(self): """Тест сериализации и сохранения строки""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.set = AsyncMock(return_value=True) result = await service.serialize_and_set("test_key", "test_value") assert result is True @pytest.mark.asyncio async def test_serialize_and_set_bytes(self): """Тест сериализации и сохранения байтов""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.set = AsyncMock(return_value=True) result = await service.serialize_and_set("test_key", b"test_value") assert result is True @pytest.mark.asyncio async def test_serialize_and_set_dict(self): """Тест сериализации и сохранения словаря""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.set = AsyncMock(return_value=True) data = {"key": "value", "number": 42} result = await service.serialize_and_set("test_key", data) assert result is True @pytest.mark.asyncio async def test_serialize_and_set_exception(self): """Тест сериализации с исключением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.set = AsyncMock(side_effect=Exception("Redis error")) result = await service.serialize_and_set("test_key", "test_value") assert result is False @pytest.mark.asyncio async def test_get_and_deserialize_success(self): """Тест успешного получения и десериализации""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.get = AsyncMock(return_value=b'{"key": "value"}') result = await service.get_and_deserialize("test_key") assert result == {"key": "value"} @pytest.mark.asyncio async def test_get_and_deserialize_string(self): """Тест получения и десериализации строки""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.get = AsyncMock(return_value='{"key": "value"}') result = await service.get_and_deserialize("test_key") assert result == {"key": "value"} @pytest.mark.asyncio async def test_get_and_deserialize_none(self): """Тест получения и десериализации None""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.get = AsyncMock(return_value=None) result = await service.get_and_deserialize("test_key") assert result is None @pytest.mark.asyncio async def test_get_and_deserialize_exception(self): """Тест получения и десериализации с исключением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.get = AsyncMock(return_value=b"invalid json") result = await service.get_and_deserialize("test_key") assert result is None class TestRedisPipeline: """Тесты pipeline операций""" @pytest.mark.asyncio async def test_pipeline_property(self): """Тест свойства pipeline""" service = RedisService() service._client = Mock() service._is_available = True mock_pipeline = Mock() service._client.pipeline.return_value = mock_pipeline result = service.pipeline() assert result == mock_pipeline @pytest.mark.asyncio async def test_pipeline_not_connected(self): """Тест pipeline без подключения""" service = RedisService() service._client = None result = service.pipeline() assert result is None @pytest.mark.asyncio async def test_execute_pipeline_success(self): """Тест успешного выполнения pipeline""" service = RedisService() service._client = Mock() service._is_available = True mock_pipeline = Mock() mock_pipeline.execute = AsyncMock(return_value=["result1", "result2"]) service._client.pipeline.return_value = mock_pipeline # Добавляем методы в pipeline mock_pipeline.set = Mock() mock_pipeline.get = Mock() commands = [ ("set", ("key1", "value1")), ("get", ("key2",)), ] result = await service.execute_pipeline(commands) assert result == ["result1", "result2"] @pytest.mark.asyncio async def test_execute_pipeline_not_connected(self): """Тест выполнения pipeline без подключения""" service = RedisService() service._client = None service._is_available = True commands = [("set", ("key1", "value1"))] result = await service.execute_pipeline(commands) assert result == [] @pytest.mark.asyncio async def test_execute_pipeline_failed_creation(self): """Тест выполнения pipeline с неудачным созданием""" service = RedisService() service._client = Mock() service._is_available = True service._client.pipeline.return_value = None commands = [("set", ("key1", "value1"))] result = await service.execute_pipeline(commands) assert result == [] @pytest.mark.asyncio async def test_execute_pipeline_unknown_command(self): """Тест выполнения pipeline с неизвестной командой""" service = RedisService() service._client = Mock() service._is_available = True mock_pipeline = Mock() mock_pipeline.execute = AsyncMock(return_value=["result1"]) service._client.pipeline.return_value = mock_pipeline # Добавляем только set метод в pipeline mock_pipeline.set = Mock() commands = [ ("set", ("key1", "value1")), ("unknown_command", ("arg1",)), ] result = await service.execute_pipeline(commands) assert result == ["result1"] @pytest.mark.asyncio async def test_execute_pipeline_exception(self): """Тест выполнения pipeline с исключением""" service = RedisService() service._client = Mock() service._is_available = True mock_pipeline = Mock() mock_pipeline.execute = AsyncMock(side_effect=Exception("Pipeline error")) service._client.pipeline.return_value = mock_pipeline commands = [("set", ("key1", "value1"))] result = await service.execute_pipeline(commands) assert result == [] class TestRedisPublish: """Тесты публикации сообщений""" @pytest.mark.asyncio async def test_publish_success(self): """Тест успешной публикации""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.publish = AsyncMock(return_value=1) await service.publish("test_channel", "test_message") service._client.publish.assert_called_once_with("test_channel", "test_message") @pytest.mark.asyncio async def test_publish_not_connected(self): """Тест публикации без подключения""" service = RedisService() service._client = None service._is_available = True # Не должно вызывать ошибку await service.publish("test_channel", "test_message") @pytest.mark.asyncio async def test_publish_exception(self): """Тест публикации с исключением""" service = RedisService() service._client = AsyncMock() service._is_available = True service._client.publish = AsyncMock(side_effect=Exception("Publish error")) # Не должно вызывать ошибку await service.publish("test_channel", "test_message") class TestGlobalRedisFunctions: """Тесты глобальных функций Redis""" @pytest.mark.asyncio async def test_init_redis(self): """Тест инициализации глобального Redis""" with patch.object(redis, "connect") as mock_connect: await init_redis() mock_connect.assert_called_once() @pytest.mark.asyncio async def test_close_redis(self): """Тест закрытия глобального Redis""" with patch.object(redis, "disconnect") as mock_disconnect: await close_redis() mock_disconnect.assert_called_once() def test_global_redis_instance(self): """Тест глобального экземпляра Redis""" assert redis is not None assert isinstance(redis, RedisService) class TestRedisLogging: """Тесты логирования Redis""" def test_redis_logger_level(self): """Тест уровня логирования Redis""" redis_logger = logging.getLogger("redis") assert redis_logger.level == logging.WARNING class TestAdditionalRedisCoverage: """Дополнительные тесты для покрытия недостающих строк Redis""" async def test_connect_exception_handling(self): """Test connect with exception during close""" service = RedisService() mock_client = AsyncMock() service._client = mock_client mock_client.close.side_effect = Exception("Close error") with patch('services.redis.aioredis.from_url') as mock_from_url: mock_new_client = AsyncMock() mock_from_url.return_value = mock_new_client await service.connect() # Should handle the exception and continue assert service._client is not None async def test_disconnect_exception_handling(self): """Test disconnect with exception""" service = RedisService() mock_client = AsyncMock() service._client = mock_client mock_client.close.side_effect = Exception("Disconnect error") # The disconnect method doesn't handle exceptions, so it should raise with pytest.raises(Exception, match="Disconnect error"): await service.close() # Since exception is not handled, client remains unchanged assert service._client is mock_client async def test_get_and_deserialize_exception(self): """Test get_and_deserialize with exception""" service = RedisService() with patch.object(service, 'get') as mock_get: mock_get.return_value = b'invalid json' result = await service.get_and_deserialize("test_key") assert result is None async def test_execute_pipeline_unknown_command_logging(self): """Test execute_pipeline with unknown command logging""" service = RedisService() mock_client = Mock() service._client = mock_client mock_pipeline = Mock() mock_client.pipeline.return_value = mock_pipeline mock_pipeline.set = Mock() mock_pipeline.get = Mock() # Test with unknown command commands = [("unknown", ("key",))] result = await service.execute_pipeline(commands) assert result == []