feat: улучшено логирование и обработка скорингов в PostService и RagApiClient - Добавлены отладочные сообщения для передачи скорингов в функции обработки постов. - Обновлено логирование успешного получения скорингов из RAG API с дополнительной информацией. - Оптимизирована обработка скорингов в функции get_text_message для улучшения отладки. - Обновлены тесты для проверки новых функциональных возможностей и обработки ошибок.
252 lines
9.7 KiB
Python
252 lines
9.7 KiB
Python
"""
|
||
Тесты для helper_bot.server_prometheus: MetricsServer, start_metrics_server, stop_metrics_server.
|
||
"""
|
||
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
import pytest
|
||
from aiohttp import web
|
||
|
||
from helper_bot.server_prometheus import (
|
||
MetricsServer,
|
||
start_metrics_server,
|
||
stop_metrics_server,
|
||
)
|
||
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.asyncio
|
||
class TestMetricsServer:
|
||
"""Тесты для класса MetricsServer."""
|
||
|
||
def test_init_sets_host_port_and_routes(self):
|
||
"""При инициализации задаются host, port и маршруты /metrics, /health."""
|
||
server = MetricsServer(host="127.0.0.1", port=9090)
|
||
assert server.host == "127.0.0.1"
|
||
assert server.port == 9090
|
||
assert server.runner is None
|
||
assert server.site is None
|
||
paths = []
|
||
for res in server.app.router.resources():
|
||
info = res.get_info()
|
||
path = info.get("path") or info.get("formatter")
|
||
if path:
|
||
paths.append(path)
|
||
assert "/metrics" in paths
|
||
assert "/health" in paths
|
||
|
||
@patch("helper_bot.server_prometheus.metrics")
|
||
async def test_metrics_handler_success_returns_prometheus_content(
|
||
self, mock_metrics_module
|
||
):
|
||
"""metrics_handler при успехе возвращает 200 и данные метрик."""
|
||
mock_metrics_module.get_metrics.return_value = b"# TYPE bot_commands_total counter"
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.metrics_handler(request)
|
||
|
||
assert response.status == 200
|
||
assert response.body == b"# TYPE bot_commands_total counter"
|
||
assert "text/plain" in response.content_type
|
||
mock_metrics_module.get_metrics.assert_called_once()
|
||
|
||
@patch("helper_bot.server_prometheus.metrics", None)
|
||
async def test_metrics_handler_when_metrics_none_returns_500(self):
|
||
"""metrics_handler при недоступности metrics возвращает 500."""
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.metrics_handler(request)
|
||
|
||
assert response.status == 500
|
||
assert "Metrics not available" in response.text
|
||
|
||
@patch("helper_bot.server_prometheus.metrics")
|
||
async def test_metrics_handler_on_exception_returns_500(
|
||
self, mock_metrics_module
|
||
):
|
||
"""metrics_handler при исключении в get_metrics возвращает 500."""
|
||
mock_metrics_module.get_metrics.side_effect = RuntimeError("metrics error")
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.metrics_handler(request)
|
||
|
||
assert response.status == 500
|
||
assert "Error generating metrics" in response.text
|
||
|
||
@patch("helper_bot.server_prometheus.metrics")
|
||
async def test_health_handler_success_returns_ok(self, mock_metrics_module):
|
||
"""health_handler при успехе возвращает 200 OK."""
|
||
mock_metrics_module.get_metrics.return_value = b"some_metrics_data"
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.health_handler(request)
|
||
|
||
assert response.status == 200
|
||
assert response.text == "OK"
|
||
|
||
@patch("helper_bot.server_prometheus.metrics", None)
|
||
async def test_health_handler_when_metrics_none_returns_503(self):
|
||
"""health_handler при недоступности metrics возвращает 503."""
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.health_handler(request)
|
||
|
||
assert response.status == 503
|
||
assert "Metrics not available" in response.text
|
||
|
||
@patch("helper_bot.server_prometheus.metrics")
|
||
async def test_health_handler_empty_metrics_returns_503(
|
||
self, mock_metrics_module
|
||
):
|
||
"""health_handler при пустых метриках возвращает 503."""
|
||
mock_metrics_module.get_metrics.return_value = b""
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.health_handler(request)
|
||
|
||
assert response.status == 503
|
||
assert "Empty metrics" in response.text
|
||
|
||
@patch("helper_bot.server_prometheus.metrics")
|
||
async def test_health_handler_get_metrics_raises_returns_503(
|
||
self, mock_metrics_module
|
||
):
|
||
"""health_handler при исключении get_metrics возвращает 503."""
|
||
mock_metrics_module.get_metrics.side_effect = ValueError("gen failed")
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
request = MagicMock(spec=web.Request)
|
||
|
||
response = await server.health_handler(request)
|
||
|
||
assert response.status == 503
|
||
assert "Metrics generation failed" in response.text
|
||
|
||
@patch("helper_bot.server_prometheus.web.AppRunner")
|
||
@patch("helper_bot.server_prometheus.web.TCPSite")
|
||
async def test_start_creates_runner_and_site(
|
||
self, mock_tcp_site_cls, mock_app_runner_cls
|
||
):
|
||
"""start() создаёт AppRunner и TCPSite и запускает сервер."""
|
||
mock_runner = MagicMock()
|
||
mock_runner.setup = AsyncMock()
|
||
mock_app_runner_cls.return_value = mock_runner
|
||
mock_site = MagicMock()
|
||
mock_site.start = AsyncMock()
|
||
mock_tcp_site_cls.return_value = mock_site
|
||
server = MetricsServer(host="0.0.0.0", port=19998)
|
||
|
||
await server.start()
|
||
|
||
mock_app_runner_cls.assert_called_once_with(server.app)
|
||
mock_runner.setup.assert_awaited_once()
|
||
mock_tcp_site_cls.assert_called_once_with(mock_runner, "0.0.0.0", 19998)
|
||
mock_site.start.assert_awaited_once()
|
||
assert server.runner is mock_runner
|
||
assert server.site is mock_site
|
||
|
||
async def test_stop_stops_site_and_cleans_runner(self):
|
||
"""stop() останавливает site и очищает runner."""
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
server.site = MagicMock()
|
||
server.site.stop = AsyncMock()
|
||
server.runner = MagicMock()
|
||
server.runner.cleanup = AsyncMock()
|
||
|
||
await server.stop()
|
||
|
||
server.site.stop.assert_awaited_once()
|
||
server.runner.cleanup.assert_awaited_once()
|
||
|
||
async def test_stop_when_site_none_does_not_raise(self):
|
||
"""stop() при site=None не падает."""
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
server.site = None
|
||
server.runner = None
|
||
|
||
await server.stop()
|
||
|
||
@patch.object(MetricsServer, "start", new_callable=AsyncMock)
|
||
@patch.object(MetricsServer, "stop", new_callable=AsyncMock)
|
||
async def test_context_manager_enters_and_exits(
|
||
self, mock_stop, mock_start
|
||
):
|
||
"""Использование как async context manager вызывает start и stop."""
|
||
mock_start.return_value = None
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
|
||
async with server:
|
||
pass
|
||
|
||
mock_start.assert_awaited_once()
|
||
mock_stop.assert_awaited_once()
|
||
|
||
@patch.object(MetricsServer, "start", new_callable=AsyncMock)
|
||
@patch.object(MetricsServer, "stop", new_callable=AsyncMock)
|
||
async def test_context_manager_exit_calls_stop_on_exception(
|
||
self, mock_stop, mock_start
|
||
):
|
||
"""При исключении внутри контекста stop всё равно вызывается."""
|
||
mock_start.return_value = None
|
||
server = MetricsServer(host="0.0.0.0", port=8080)
|
||
|
||
with pytest.raises(ValueError):
|
||
async with server:
|
||
raise ValueError("test")
|
||
|
||
mock_stop.assert_awaited_once()
|
||
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.asyncio
|
||
class TestStartStopMetricsServer:
|
||
"""Тесты для start_metrics_server и stop_metrics_server."""
|
||
|
||
@patch("helper_bot.server_prometheus.MetricsServer")
|
||
async def test_start_metrics_server_creates_and_starts_server(
|
||
self, mock_server_cls
|
||
):
|
||
"""start_metrics_server создаёт MetricsServer и вызывает start()."""
|
||
mock_instance = MagicMock()
|
||
mock_instance.start = AsyncMock()
|
||
mock_server_cls.return_value = mock_instance
|
||
|
||
result = await start_metrics_server("0.0.0.0", 8080)
|
||
|
||
mock_server_cls.assert_called_once_with("0.0.0.0", 8080)
|
||
mock_instance.start.assert_awaited_once()
|
||
assert result is mock_instance
|
||
|
||
@patch("helper_bot.server_prometheus.MetricsServer")
|
||
async def test_stop_metrics_server_when_running_stops_and_clears_global(
|
||
self, mock_server_cls
|
||
):
|
||
"""stop_metrics_server при запущенном сервере останавливает его и обнуляет глобальную переменную."""
|
||
import helper_bot.server_prometheus as mod
|
||
mock_instance = MagicMock()
|
||
mock_instance.stop = AsyncMock()
|
||
old_server = mod.metrics_server
|
||
mod.metrics_server = mock_instance
|
||
try:
|
||
await stop_metrics_server()
|
||
mock_instance.stop.assert_awaited_once()
|
||
assert mod.metrics_server is None
|
||
finally:
|
||
mod.metrics_server = old_server
|
||
|
||
async def test_stop_metrics_server_when_none_does_not_raise(self):
|
||
"""stop_metrics_server при metrics_server=None не падает."""
|
||
import helper_bot.server_prometheus as mod
|
||
old_server = mod.metrics_server
|
||
mod.metrics_server = None
|
||
try:
|
||
await stop_metrics_server()
|
||
assert mod.metrics_server is None
|
||
finally:
|
||
mod.metrics_server = old_server
|