304 lines
12 KiB
Python
304 lines
12 KiB
Python
"""
|
||
Качественные тесты функциональности Redis сервиса.
|
||
|
||
Тестируем реальное поведение, а не просто наличие методов.
|
||
"""
|
||
|
||
import pytest
|
||
import asyncio
|
||
import json
|
||
from services.redis import RedisService
|
||
|
||
|
||
class TestRedisFunctionality:
|
||
"""Тесты реальной функциональности Redis"""
|
||
|
||
@pytest.fixture
|
||
async def redis_service(self):
|
||
"""Создает тестовый Redis сервис"""
|
||
service = RedisService("redis://localhost:6379/1") # Используем БД 1 для тестов
|
||
await service.connect()
|
||
yield service
|
||
await service.disconnect()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_connection_lifecycle(self, redis_service):
|
||
"""Тест жизненного цикла подключения к Redis"""
|
||
# Проверяем что подключение активно
|
||
assert redis_service.is_connected is True
|
||
|
||
# Отключаемся
|
||
await redis_service.disconnect()
|
||
assert redis_service.is_connected is False
|
||
|
||
# Подключаемся снова
|
||
await redis_service.connect()
|
||
assert redis_service.is_connected is True
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_basic_operations(self, redis_service):
|
||
"""Тест базовых операций Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест SET/GET
|
||
await redis_service.set("test_key", "test_value")
|
||
result = await redis_service.get("test_key")
|
||
assert result == "test_value"
|
||
|
||
# Тест SET с TTL - используем правильный параметр 'ex'
|
||
await redis_service.set("test_key_ttl", "test_value_ttl", ex=1)
|
||
result = await redis_service.get("test_key_ttl")
|
||
assert result == "test_value_ttl"
|
||
|
||
# Ждем истечения TTL
|
||
await asyncio.sleep(1.1)
|
||
result = await redis_service.get("test_key_ttl")
|
||
assert result is None
|
||
|
||
# Тест DELETE
|
||
await redis_service.set("test_key_delete", "test_value")
|
||
await redis_service.delete("test_key_delete")
|
||
result = await redis_service.get("test_key_delete")
|
||
assert result is None
|
||
|
||
# Тест EXISTS
|
||
await redis_service.set("test_key_exists", "test_value")
|
||
exists = await redis_service.exists("test_key_exists")
|
||
assert exists is True
|
||
|
||
exists = await redis_service.exists("non_existent_key")
|
||
assert exists is False
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_hash_operations(self, redis_service):
|
||
"""Тест операций с хешами Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест HSET/HGET
|
||
await redis_service.hset("test_hash", "field1", "value1")
|
||
await redis_service.hset("test_hash", "field2", "value2")
|
||
|
||
result = await redis_service.hget("test_hash", "field1")
|
||
assert result == "value1"
|
||
|
||
result = await redis_service.hget("test_hash", "field2")
|
||
assert result == "value2"
|
||
|
||
# Тест HGETALL
|
||
all_fields = await redis_service.hgetall("test_hash")
|
||
assert all_fields == {"field1": "value1", "field2": "value2"}
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_set_operations(self, redis_service):
|
||
"""Тест операций с множествами Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест SADD
|
||
await redis_service.sadd("test_set", "member1")
|
||
await redis_service.sadd("test_set", "member2")
|
||
await redis_service.sadd("test_set", "member3")
|
||
|
||
# Тест SMEMBERS
|
||
members = await redis_service.smembers("test_set")
|
||
assert len(members) == 3
|
||
assert "member1" in members
|
||
assert "member2" in members
|
||
assert "member3" in members
|
||
|
||
# Тест SREM
|
||
await redis_service.srem("test_set", "member2")
|
||
members = await redis_service.smembers("test_set")
|
||
assert len(members) == 2
|
||
assert "member2" not in members
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_serialization(self, redis_service):
|
||
"""Тест сериализации/десериализации данных"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест с простыми типами
|
||
test_data = {
|
||
"string": "test_string",
|
||
"number": 42,
|
||
"boolean": True,
|
||
"list": [1, 2, 3],
|
||
"dict": {"nested": "value"}
|
||
}
|
||
|
||
# Сериализуем и сохраняем
|
||
await redis_service.serialize_and_set("test_serialization", test_data)
|
||
|
||
# Получаем и десериализуем
|
||
result = await redis_service.get_and_deserialize("test_serialization")
|
||
assert result == test_data
|
||
|
||
# Тест с None
|
||
await redis_service.serialize_and_set("test_none", None)
|
||
result = await redis_service.get_and_deserialize("test_none")
|
||
assert result is None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_pipeline(self, redis_service):
|
||
"""Тест pipeline операций Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Создаем pipeline через правильный метод
|
||
pipeline = redis_service.pipeline()
|
||
assert pipeline is not None
|
||
|
||
# Добавляем команды в pipeline
|
||
pipeline.set("key1", "value1")
|
||
pipeline.set("key2", "value2")
|
||
pipeline.set("key3", "value3")
|
||
|
||
# Выполняем pipeline
|
||
results = await pipeline.execute()
|
||
|
||
# Проверяем результаты
|
||
assert len(results) == 3
|
||
|
||
# Проверяем что данные сохранились
|
||
value1 = await redis_service.get("key1")
|
||
value2 = await redis_service.get("key2")
|
||
value3 = await redis_service.get("key3")
|
||
|
||
assert value1 == "value1"
|
||
assert value2 == "value2"
|
||
assert value3 == "value3"
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_publish_subscribe(self, redis_service):
|
||
"""Тест pub/sub функциональности Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Создаем список для хранения полученных сообщений
|
||
received_messages = []
|
||
|
||
# Функция для обработки сообщений
|
||
async def message_handler(channel, message):
|
||
received_messages.append((channel, message))
|
||
|
||
# Подписываемся на канал - используем правильный способ
|
||
# Создаем pubsub объект из клиента
|
||
if redis_service._client:
|
||
pubsub = redis_service._client.pubsub()
|
||
await pubsub.subscribe("test_channel")
|
||
|
||
# Запускаем прослушивание в фоне
|
||
async def listen_messages():
|
||
async for message in pubsub.listen():
|
||
if message["type"] == "message":
|
||
await message_handler(message["channel"], message["data"])
|
||
|
||
# Запускаем прослушивание
|
||
listener_task = asyncio.create_task(listen_messages())
|
||
|
||
# Ждем немного для установки соединения
|
||
await asyncio.sleep(0.1)
|
||
|
||
# Публикуем сообщение
|
||
await redis_service.publish("test_channel", "test_message")
|
||
|
||
# Ждем получения сообщения
|
||
await asyncio.sleep(0.1)
|
||
|
||
# Останавливаем прослушивание
|
||
listener_task.cancel()
|
||
await pubsub.unsubscribe("test_channel")
|
||
await pubsub.close()
|
||
|
||
# Проверяем что сообщение получено
|
||
assert len(received_messages) > 0
|
||
|
||
# Проверяем канал и сообщение - учитываем возможные различия в кодировке
|
||
channel = received_messages[0][0]
|
||
message = received_messages[0][1]
|
||
|
||
# Канал может быть в байтах или строке
|
||
if isinstance(channel, bytes):
|
||
channel = channel.decode('utf-8')
|
||
assert channel == "test_channel"
|
||
|
||
# Сообщение может быть в байтах или строке
|
||
if isinstance(message, bytes):
|
||
message = message.decode('utf-8')
|
||
assert message == "test_message"
|
||
else:
|
||
pytest.skip("Redis client not available")
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_error_handling(self, redis_service):
|
||
"""Тест обработки ошибок Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест с несуществующей командой
|
||
try:
|
||
await redis_service.execute("NONEXISTENT_COMMAND")
|
||
print("⚠️ Несуществующая команда выполнилась без ошибки")
|
||
except Exception as e:
|
||
print(f"✅ Ошибка обработана корректно: {e}")
|
||
|
||
# Тест с неправильными аргументами
|
||
try:
|
||
await redis_service.execute("SET", "key") # Недостаточно аргументов
|
||
print("⚠️ SET с недостаточными аргументами выполнился без ошибки")
|
||
except Exception as e:
|
||
print(f"✅ Ошибка обработана корректно: {e}")
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_performance(self, redis_service):
|
||
"""Тест производительности Redis операций"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Тест массовой записи
|
||
start_time = asyncio.get_event_loop().time()
|
||
|
||
for i in range(100):
|
||
await redis_service.set(f"perf_key_{i}", f"perf_value_{i}")
|
||
|
||
write_time = asyncio.get_event_loop().time() - start_time
|
||
|
||
# Тест массового чтения
|
||
start_time = asyncio.get_event_loop().time()
|
||
|
||
for i in range(100):
|
||
await redis_service.get(f"perf_key_{i}")
|
||
|
||
read_time = asyncio.get_event_loop().time() - start_time
|
||
|
||
# Проверяем что операции выполняются достаточно быстро
|
||
assert write_time < 1.0 # Запись 100 ключей должна занимать менее 1 секунды
|
||
assert read_time < 1.0 # Чтение 100 ключей должно занимать менее 1 секунды
|
||
|
||
print(f"Write time: {write_time:.3f}s, Read time: {read_time:.3f}s")
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_redis_data_persistence(self, redis_service):
|
||
"""Тест персистентности данных Redis"""
|
||
# Очищаем тестовую БД
|
||
await redis_service.execute("FLUSHDB")
|
||
|
||
# Сохраняем данные
|
||
test_data = {"persistent": "data", "number": 123}
|
||
await redis_service.serialize_and_set("persistent_key", test_data)
|
||
|
||
# Проверяем что данные сохранились
|
||
result = await redis_service.get_and_deserialize("persistent_key")
|
||
assert result == test_data
|
||
|
||
# Переподключаемся к Redis
|
||
await redis_service.disconnect()
|
||
await redis_service.connect()
|
||
|
||
# Проверяем что данные все еще доступны
|
||
result = await redis_service.get_and_deserialize("persistent_key")
|
||
assert result == test_data
|