""" Тесты для helper_bot.services.scoring.deepseek_service (DeepSeekService). """ from unittest.mock import AsyncMock, MagicMock, patch import pytest from helper_bot.services.scoring.deepseek_service import DeepSeekService from helper_bot.services.scoring.exceptions import ( DeepSeekAPIError, ScoringError, TextTooShortError, ) @pytest.mark.unit class TestDeepSeekServiceInit: """Тесты инициализации DeepSeekService.""" def test_init_with_api_key_enabled(self): """При переданном api_key сервис включён.""" with patch( "helper_bot.services.scoring.deepseek_service.httpx.AsyncClient", None ): service = DeepSeekService(api_key="key") assert service.is_enabled is True assert service.source_name == "deepseek" def test_init_without_api_key_disabled(self): """Без api_key сервис отключён.""" service = DeepSeekService(api_key=None) assert service.is_enabled is False def test_init_default_url_and_model(self): """Используются DEFAULT_API_URL и DEFAULT_MODEL.""" service = DeepSeekService(api_key="k") assert service.api_url == DeepSeekService.DEFAULT_API_URL assert service.model == DeepSeekService.DEFAULT_MODEL @pytest.mark.unit class TestDeepSeekServiceHelpers: """Тесты _clean_text и _parse_score_response.""" def test_clean_text_strips_and_collapses_whitespace(self): """_clean_text убирает лишние пробелы и переносы.""" service = DeepSeekService(api_key="k") assert service._clean_text(" a b \n c ") == "a b c" def test_clean_text_empty_returns_empty(self): """_clean_text для пустой строки возвращает ''.""" service = DeepSeekService(api_key="k") assert service._clean_text("") == "" assert service._clean_text(" ") == "" def test_parse_score_response_valid_number(self): """_parse_score_response парсит число.""" service = DeepSeekService(api_key="k") assert service._parse_score_response("0.75") == 0.75 assert service._parse_score_response("1.0") == 1.0 assert service._parse_score_response("0") == 0.0 def test_parse_score_response_clamps_to_range(self): """_parse_score_response ограничивает значение 0.0–1.0.""" service = DeepSeekService(api_key="k") assert service._parse_score_response("1.5") == 1.0 assert service._parse_score_response("-0.1") == 0.0 def test_parse_score_response_invalid_raises(self): """_parse_score_response при невалидном ответе выбрасывает DeepSeekAPIError.""" service = DeepSeekService(api_key="k") with pytest.raises(DeepSeekAPIError, match="распарсить"): service._parse_score_response("not a number") @pytest.mark.unit @pytest.mark.asyncio class TestDeepSeekServiceCalculateScore: """Тесты calculate_score.""" @pytest.fixture def service(self): """Сервис с api_key.""" return DeepSeekService(api_key="key", min_text_length=3) async def test_disabled_raises_scoring_error(self, service): """При отключённом сервисе — ScoringError.""" service._enabled = False with pytest.raises(ScoringError, match="отключен"): await service.calculate_score("достаточно длинный текст") async def test_text_too_short_raises(self, service): """Текст короче min_text_length — TextTooShortError.""" with pytest.raises(TextTooShortError, match="короткий"): await service.calculate_score("ab") async def test_success_returns_scoring_result(self, service): """Успешный запрос возвращает ScoringResult.""" with patch.object( service, "_make_api_request", new_callable=AsyncMock, return_value=0.82, ): result = await service.calculate_score("Текст поста для оценки") assert result.score == 0.82 assert result.source == "deepseek" assert result.model == service.model @pytest.mark.unit class TestDeepSeekServiceStats: """Тесты get_stats.""" def test_get_stats_returns_dict(self): """get_stats возвращает словарь с enabled, model, api_url, timeout, max_retries.""" service = DeepSeekService(api_key="k", timeout=60, max_retries=5) stats = service.get_stats() assert stats["enabled"] is True assert stats["model"] == service.model assert stats["api_url"] == service.api_url assert stats["timeout"] == 60 assert stats["max_retries"] == 5