Files
telegram-helper-bot/tests/test_server_prometheus.py
2026-02-02 00:54:23 +03:00

250 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Тесты для 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