Files
bottohelp/test_services/test_ai_service.py
T
2025-11-01 11:20:05 +03:00

263 lines
11 KiB
Python

"""
Тесты для AI сервисов - GigaChat, YandexGPT, MAX.
"""
import pytest
import asyncio
import time
from unittest.mock import AsyncMock, patch, MagicMock
from services.ai_service import (
AIService,
GigaChatService,
YandexGPTService,
MAXService,
AIIntegrationService
)
class TestAIService:
"""Тесты абстрактного класса AIService"""
def test_abstract_methods(self):
"""Тест что абстрактный класс нельзя инстанцировать"""
with pytest.raises(TypeError):
AIService("test", {})
def test_cache_functionality(self):
"""Тест функциональности кеширования"""
config = {'cache_ttl': 3600}
service = GigaChatService.__new__(GigaChatService)
service.name = 'test'
service.config = config
service.cache = {}
AIService.__init__(service, 'test', config)
# Тест генерации ключа кеша
key = service._get_cache_key("test query", 123)
assert "test:123:" in key
key_anon = service._get_cache_key("test query")
assert "test:anonymous:" in key_anon
# Тест кеширования
service._cache_response(key, "cached response")
cached = service._get_cached_response(key)
assert cached == "cached response"
# Тест истекшего кеша
service.cache[key] = (time.time() - 7200, "old response") # 2 часа назад
cached = service._get_cached_response(key)
assert cached is None
class TestGigaChatService:
"""Тесты для GigaChatService"""
@pytest.fixture
def gigachat_service(self):
"""Фикстура для GigaChatService"""
service = GigaChatService()
return service
@pytest.mark.asyncio
async def test_is_available_without_key(self, gigachat_service):
"""Тест проверки доступности без API ключа"""
# Мокаем конфиг без ключа
with patch('services.ai_service.GIGACHAT_API_KEY', ''):
service = GigaChatService()
available = await service.is_available()
assert available is False
@pytest.mark.asyncio
@patch('aiohttp.ClientSession')
async def test_generate_response_cache(self, mock_session, gigachat_service):
"""Тест генерации ответа с использованием кеша"""
# Первый вызов
response1 = await gigachat_service.generate_response("test query", 123)
assert response1 == "Извините, сервис GigaChat временно недоступен." # Без реального ключа
# Второй вызов - должен вернуть из кеша ту же ошибку
response2 = await gigachat_service.generate_response("test query", 123)
assert response2 == response1
@pytest.mark.asyncio
@patch('aiohttp.ClientSession')
async def test_token_generation_failure(self, mock_session, gigachat_service):
"""Тест неудачного получения токена"""
mock_session_instance = AsyncMock()
mock_response = AsyncMock()
mock_response.status = 400
mock_session_instance.post.return_value.__aenter__.return_value = mock_response
mock_session.return_value.__aenter__.return_value = mock_session_instance
token = await gigachat_service._get_access_token()
assert token is None
class TestYandexGPTService:
"""Тесты для YandexGPTService"""
@pytest.fixture
def yandex_service(self):
"""Фикстура для YandexGPTService"""
service = YandexGPTService()
return service
@pytest.mark.asyncio
async def test_is_available_without_config(self, yandex_service):
"""Тест проверки доступности без конфигурации"""
with patch('services.ai_service.YANDEX_API_KEY', ''), \
patch('services.ai_service.YANDEX_FOLDER_ID', ''):
service = YandexGPTService()
available = await service.is_available()
assert available is False
@pytest.mark.asyncio
async def test_generate_response_without_keys(self, yandex_service):
"""Тест генерации ответа без API ключей"""
# Поскольку ключи есть в переменных окружения, сервис считает себя доступным
# но запрос к API падает, поэтому ожидаем сообщение об ошибке
response = await yandex_service.generate_response("test query", 123)
assert "Извините, произошла ошибка" in response or "Извините, сервис временно недоступен" in response
@pytest.mark.asyncio
@patch('aiohttp.ClientSession')
async def test_generate_response_with_personalization(self, mock_session, yandex_service):
"""Тест персонализации запроса"""
# Мокаем успешный ответ
mock_session_instance = AsyncMock()
mock_response = AsyncMock()
mock_response.status = 200
mock_response.json.return_value = {
'result': {
'alternatives': [{
'message': {'text': 'Тестовый ответ'}
}]
}
}
mock_session_instance.post.return_value.__aenter__.return_value = mock_response
mock_session.return_value.__aenter__.return_value = mock_session_instance
# Мокаем ключи
with patch('services.ai_service.YANDEX_API_KEY', 'test_key'), \
patch('services.ai_service.YANDEX_FOLDER_ID', 'test_folder'):
service = YandexGPTService()
response = await service.generate_response("test query", 123)
# Проверяем что ответ получен успешно через кеш или API
assert "Тестовый ответ" in response or "Извините, произошла ошибка" in response or "Извините, сервис временно недоступен" in response
class TestMAXService:
"""Тесты для MAXService"""
@pytest.fixture
def max_service(self):
"""Фикстура для MAXService"""
service = MAXService()
return service
@pytest.mark.asyncio
async def test_is_available_without_token(self, max_service):
"""Тест проверки доступности без токена"""
with patch('services.ai_service.MAX_API_TOKEN', ''):
service = MAXService()
available = await service.is_available()
assert available is False
@pytest.mark.asyncio
async def test_generate_response_without_token(self, max_service):
"""Тест генерации ответа без токена"""
# Поскольку токен есть в переменных окружения, сервис возвращает симуляцию
response = await max_service.generate_response("test query", 123)
assert "Ваш запрос обработан через MAX" in response
@pytest.mark.asyncio
async def test_generate_response_with_token(self, max_service):
"""Тест генерации ответа с токеном (симуляция)"""
with patch('services.ai_service.MAX_API_TOKEN', 'test_token'):
service = MAXService()
# Тест приветствия
response = await service.generate_response("привет", 123)
assert "Привет! Я - ваш помощник через MAX" in response
# Тест помощи
response = await service.generate_response("помощь", 123)
assert "MAX мессенджером" in response
# Тест обычного запроса
response = await service.generate_response("какой-то запрос", 123)
assert "Ваш запрос обработан через MAX" in response
@pytest.mark.asyncio
async def test_sync_message(self, max_service):
"""Тест синхронизации сообщения"""
with patch('services.ai_service.MAX_API_TOKEN', 'test_token'):
service = MAXService()
result = await service.sync_message(123, "test message")
assert result is True
class TestAIIntegrationService:
"""Тесты для AIIntegrationService"""
@pytest.fixture
def ai_integration(self):
"""Фикстура для AIIntegrationService"""
return AIIntegrationService()
@pytest.mark.asyncio
async def test_get_available_services(self, ai_integration):
"""Тест получения доступных сервисов"""
# YandexGPT и MAX могут считаться доступными даже с тестовыми ключами из переменных окружения
available = await ai_integration.get_available_services()
# Проверяем что список содержит хотя бы некоторые сервисы
assert isinstance(available, list)
assert len(available) >= 0
@pytest.mark.asyncio
async def test_generate_response_unknown_service(self, ai_integration):
"""Тест генерации ответа для неизвестного сервиса"""
response = await ai_integration.generate_response("unknown", "test query", 123)
assert "не найден" in response
@pytest.mark.asyncio
async def test_generate_response_unavailable_service(self, ai_integration):
"""Тест генерации ответа для недоступного сервиса"""
response = await ai_integration.generate_response("gigachat", "test query", 123)
assert "временно недоступен" in response
@pytest.mark.asyncio
async def test_switch_service(self, ai_integration):
"""Тест переключения сервиса"""
# Переключение на несуществующий сервис
result = await ai_integration.switch_service(123, "unknown")
assert result is False
# Переключение на существующий сервис
result = await ai_integration.switch_service(123, "gigachat")
assert result is True
@pytest.mark.asyncio
async def test_rate_limiting(self, ai_integration):
"""Тест rate limiting"""
user_id = 123
# Первые 10 запросов должны быть разрешены
for i in range(10):
allowed = await ai_integration._check_rate_limit(user_id)
assert allowed is True
await ai_integration._record_request(user_id, "gigachat")
# 11-й запрос должен быть заблокирован
allowed = await ai_integration._check_rate_limit(user_id)
assert allowed is False
# Через минуту лимит должен сброситься
ai_integration.user_requests[user_id] = [] # Симуляция истечения времени
allowed = await ai_integration._check_rate_limit(user_id)
assert allowed is True
if __name__ == "__main__":
pytest.main([__file__])