Переписал почти все тесты

feat: улучшено логирование и обработка скорингов в PostService и RagApiClient

- Добавлены отладочные сообщения для передачи скорингов в функции обработки постов.
- Обновлено логирование успешного получения скорингов из RAG API с дополнительной информацией.
- Оптимизирована обработка скорингов в функции get_text_message для улучшения отладки.
- Обновлены тесты для проверки новых функциональных возможностей и обработки ошибок.
This commit is contained in:
2026-01-30 00:55:47 +03:00
parent e87f4af82f
commit a5faa4bdc6
27 changed files with 4320 additions and 8 deletions

View File

@@ -0,0 +1,263 @@
"""
Тесты для helper_bot.utils.rate_limit_monitor.
"""
import time
from collections import deque
from unittest.mock import patch
import pytest
from helper_bot.utils.rate_limit_monitor import (
RateLimitMonitor,
RateLimitStats,
get_rate_limit_summary,
record_rate_limit_request,
)
@pytest.mark.unit
class TestRateLimitStats:
"""Тесты для RateLimitStats."""
def test_success_rate_zero_requests(self):
"""При нуле запросов success_rate равен 1.0."""
stats = RateLimitStats(chat_id=1)
assert stats.success_rate == 1.0
def test_success_rate_all_success(self):
"""При всех успешных запросах success_rate равен 1.0."""
stats = RateLimitStats(chat_id=1, total_requests=5, successful_requests=5)
assert stats.success_rate == 1.0
def test_success_rate_partial(self):
"""Частичный успех: 3 из 5."""
stats = RateLimitStats(chat_id=1, total_requests=5, successful_requests=3)
assert stats.success_rate == 0.6
def test_error_rate(self):
"""error_rate = 1 - success_rate."""
stats = RateLimitStats(chat_id=1, total_requests=10, successful_requests=7)
assert stats.error_rate == pytest.approx(0.3)
def test_average_wait_time_zero_requests(self):
"""При нуле запросов average_wait_time равен 0."""
stats = RateLimitStats(chat_id=1)
assert stats.average_wait_time == 0.0
def test_average_wait_time(self):
"""Среднее время ожидания считается корректно."""
stats = RateLimitStats(
chat_id=1, total_requests=4, total_wait_time=2.0
)
assert stats.average_wait_time == 0.5
def test_requests_per_minute_empty(self):
"""При пустом request_times возвращается 0."""
stats = RateLimitStats(chat_id=1)
assert stats.requests_per_minute == 0.0
def test_requests_per_minute_recent(self):
"""Подсчёт запросов за последнюю минуту."""
now = time.time()
stats = RateLimitStats(chat_id=1, request_times=deque([now, now - 30], maxlen=100))
assert stats.requests_per_minute == 2
def test_requests_per_minute_old_ignored(self):
"""Запросы старше минуты не учитываются."""
now = time.time()
stats = RateLimitStats(
chat_id=1,
request_times=deque([now, now - 90], maxlen=100),
)
assert stats.requests_per_minute == 1
@pytest.mark.unit
class TestRateLimitMonitor:
"""Тесты для RateLimitMonitor."""
def test_init(self):
"""Инициализация с дефолтными и кастомными параметрами."""
monitor = RateLimitMonitor(max_history_size=500)
assert monitor.max_history_size == 500
assert monitor.global_stats.chat_id == 0
assert len(monitor.stats) == 0
assert len(monitor.error_history) == 0
def test_record_request_success(self):
"""Запись успешного запроса обновляет счётчики."""
monitor = RateLimitMonitor()
monitor.record_request(chat_id=123, success=True, wait_time=1.5)
chat_stats = monitor.get_chat_stats(123)
assert chat_stats is not None
assert chat_stats.total_requests == 1
assert chat_stats.successful_requests == 1
assert chat_stats.failed_requests == 0
assert chat_stats.total_wait_time == 1.5
global_stats = monitor.get_global_stats()
assert global_stats.total_requests == 1
assert global_stats.successful_requests == 1
def test_record_request_failure_retry_after(self):
"""Запись ошибки RetryAfter увеличивает retry_after_errors."""
monitor = RateLimitMonitor()
monitor.record_request(chat_id=456, success=False, error_type="RetryAfter")
chat_stats = monitor.get_chat_stats(456)
assert chat_stats.failed_requests == 1
assert chat_stats.retry_after_errors == 1
assert chat_stats.other_errors == 0
assert len(monitor.error_history) == 1
assert monitor.error_history[0]["error_type"] == "RetryAfter"
def test_record_request_failure_other(self):
"""Запись другой ошибки увеличивает other_errors."""
monitor = RateLimitMonitor()
monitor.record_request(chat_id=789, success=False, error_type="Timeout")
chat_stats = monitor.get_chat_stats(789)
assert chat_stats.other_errors == 1
assert chat_stats.retry_after_errors == 0
def test_get_chat_stats_missing(self):
"""Для неизвестного чата возвращается None."""
monitor = RateLimitMonitor()
assert monitor.get_chat_stats(999) is None
def test_get_top_chats_by_requests(self):
"""Топ чатов по количеству запросов."""
monitor = RateLimitMonitor()
monitor.record_request(1, True)
monitor.record_request(1, True)
monitor.record_request(2, True)
monitor.record_request(3, True)
monitor.record_request(3, True)
monitor.record_request(3, True)
top = monitor.get_top_chats_by_requests(limit=2)
assert len(top) == 2
assert top[0][0] == 3
assert top[0][1].total_requests == 3
assert top[1][0] == 1
assert top[1][1].total_requests == 2
def test_get_chats_with_high_error_rate(self):
"""Чаты с высоким процентом ошибок (и более 5 запросов)."""
monitor = RateLimitMonitor()
for _ in range(6):
monitor.record_request(100, True)
for _ in range(4):
monitor.record_request(100, False, error_type="Other")
# 4/10 = 40% ошибок
for _ in range(6):
monitor.record_request(200, True)
for _ in range(2):
monitor.record_request(200, False, error_type="Other")
# 2/8 < 20%, но порог 0.1 — попадёт если error_rate > 0.1
high = monitor.get_chats_with_high_error_rate(threshold=0.2)
assert len(high) >= 1
chat_ids = [c[0] for c in high]
assert 100 in chat_ids
def test_get_recent_errors(self):
"""Недавние ошибки за указанный период."""
monitor = RateLimitMonitor()
monitor.record_request(1, False, error_type="RetryAfter")
recent = monitor.get_recent_errors(minutes=60)
assert len(recent) == 1
assert recent[0]["error_type"] == "RetryAfter"
assert recent[0]["chat_id"] == 1
def test_get_recent_errors_empty_old_window(self):
"""При окне 0 минут недавних ошибок нет (все старше)."""
monitor = RateLimitMonitor()
monitor.record_request(1, False, error_type="RetryAfter")
recent = monitor.get_recent_errors(minutes=0)
assert len(recent) == 0
def test_get_error_summary(self):
"""Сводка ошибок по типам."""
monitor = RateLimitMonitor()
monitor.record_request(1, False, error_type="RetryAfter")
monitor.record_request(1, False, error_type="RetryAfter")
monitor.record_request(2, False, error_type="Timeout")
summary = monitor.get_error_summary(minutes=60)
assert summary["RetryAfter"] == 2
assert summary["Timeout"] == 1
def test_reset_stats_all(self):
"""Сброс всей статистики."""
monitor = RateLimitMonitor()
monitor.record_request(1, True)
monitor.record_request(2, False, error_type="RetryAfter")
monitor.reset_stats()
assert monitor.get_chat_stats(1) is None
assert monitor.get_global_stats().total_requests == 0
assert len(monitor.error_history) == 0
def test_reset_stats_single_chat(self):
"""Сброс статистики для одного чата."""
monitor = RateLimitMonitor()
monitor.record_request(1, True)
monitor.record_request(2, True)
monitor.reset_stats(chat_id=1)
assert monitor.get_chat_stats(1) is None
assert monitor.get_chat_stats(2) is not None
assert monitor.get_global_stats().total_requests == 2
def test_reset_stats_nonexistent_chat(self):
"""Сброс несуществующего чата не падает."""
monitor = RateLimitMonitor()
monitor.reset_stats(chat_id=999)
@patch("helper_bot.utils.rate_limit_monitor.logger")
def test_log_statistics(self, mock_logger):
"""log_statistics вызывает logger с нужным уровнем."""
monitor = RateLimitMonitor()
monitor.record_request(1, True)
monitor.log_statistics(log_level="info")
mock_logger.info.assert_called()
mock_logger.reset_mock()
monitor.log_statistics(log_level="warning")
mock_logger.warning.assert_called()
mock_logger.reset_mock()
monitor.log_statistics(log_level="error")
mock_logger.error.assert_called()
@pytest.mark.unit
class TestModuleFunctions:
"""Тесты для функций модуля record_rate_limit_request и get_rate_limit_summary."""
def test_record_rate_limit_request(self):
"""record_rate_limit_request делегирует в глобальный монитор."""
monitor = RateLimitMonitor()
with patch("helper_bot.utils.rate_limit_monitor.rate_limit_monitor", monitor):
record_rate_limit_request(chat_id=111, success=True, wait_time=0.5)
stats = monitor.get_chat_stats(111)
assert stats is not None
assert stats.total_requests == 1
assert stats.total_wait_time == 0.5
def test_get_rate_limit_summary(self):
"""get_rate_limit_summary возвращает словарь с ожидаемыми ключами."""
monitor = RateLimitMonitor()
monitor.record_request(1, True)
with patch("helper_bot.utils.rate_limit_monitor.rate_limit_monitor", monitor):
summary = get_rate_limit_summary()
assert "total_requests" in summary
assert "success_rate" in summary
assert "error_rate" in summary
assert "recent_errors_count" in summary
assert "active_chats" in summary
assert "requests_per_minute" in summary
assert "average_wait_time" in summary
assert summary["total_requests"] == 1
assert summary["active_chats"] == 1