""" BATON-FIX-011: Проверяет, что BOT_TOKEN не попадает в httpx-логи. 1. logging.getLogger('httpx').level >= logging.WARNING после импорта приложения. 2. Дочерние логгеры httpx._client и httpx._async_client также не пишут INFO. 3. При вызове send_message ни одна запись httpx-логгера с уровнем INFO не содержит 'bot' или токен-подобный паттерн /bot[0-9]+:/. """ from __future__ import annotations import logging import re import httpx import pytest import respx from unittest.mock import patch, AsyncMock # conftest.py уже устанавливает BOT_TOKEN=test-bot-token до этого импорта from backend import config from backend.telegram import send_message SEND_URL = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendMessage" BOT_TOKEN_PATTERN = re.compile(r"bot[0-9]+:") # --------------------------------------------------------------------------- # Уровень логгера httpx # --------------------------------------------------------------------------- def test_httpx_logger_level_is_warning_or_higher(): """logging.getLogger('httpx').level должен быть WARNING (30) или выше после импорта приложения.""" # Импортируем main, чтобы гарантировать, что setLevel уже вызван import backend.main # noqa: F401 httpx_logger = logging.getLogger("httpx") assert httpx_logger.level >= logging.WARNING, ( f"Ожидался уровень >= WARNING (30), получен {httpx_logger.level}" ) def test_httpx_logger_info_not_enabled(): """logging.getLogger('httpx').isEnabledFor(INFO) должен возвращать False.""" import backend.main # noqa: F401 httpx_logger = logging.getLogger("httpx") assert not httpx_logger.isEnabledFor(logging.INFO), ( "httpx-логгер не должен обрабатывать INFO-сообщения" ) def test_httpx_client_logger_info_not_enabled(): """Дочерний логгер httpx._client не должен обрабатывать INFO.""" import backend.main # noqa: F401 child_logger = logging.getLogger("httpx._client") assert not child_logger.isEnabledFor(logging.INFO), ( "httpx._client не должен обрабатывать INFO-сообщения" ) def test_httpx_async_client_logger_info_not_enabled(): """Дочерний логгер httpx._async_client не должен обрабатывать INFO.""" import backend.main # noqa: F401 child_logger = logging.getLogger("httpx._async_client") assert not child_logger.isEnabledFor(logging.INFO), ( "httpx._async_client не должен обрабатывать INFO-сообщения" ) # --------------------------------------------------------------------------- # BOT_TOKEN не появляется в httpx INFO-логах при send_message # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_send_message_no_httpx_records_at_warning_level(caplog): """При вызове send_message httpx не выдаёт записей уровня WARNING и ниже с токеном. Проверяет фактическое состояние логгера в продакшне (WARNING): INFO-сообщения с URL (включая BOT_TOKEN) не должны проходить через httpx-логгер. """ import backend.main # noqa: F401 — убеждаемся, что setLevel вызван with respx.mock(assert_all_called=False) as mock: mock.post(SEND_URL).mock(return_value=httpx.Response(200, json={"ok": True})) # Захватываем логи при реальном уровне WARNING — INFO-сообщения не должны проходить with caplog.at_level(logging.WARNING, logger="httpx"): await send_message("test message for token leak check") bot_token = config.BOT_TOKEN httpx_records = [r for r in caplog.records if r.name.startswith("httpx")] for record in httpx_records: assert bot_token not in record.message, ( f"BOT_TOKEN найден в httpx-логе (уровень {record.levelname}): {record.message!r}" ) @pytest.mark.asyncio async def test_send_message_no_token_pattern_in_httpx_info_logs(caplog): """При вызове send_message httpx INFO-логи не содержат паттерн /bot[0-9]+:/.""" with respx.mock(assert_all_called=False) as mock: mock.post(SEND_URL).mock(return_value=httpx.Response(200, json={"ok": True})) with caplog.at_level(logging.INFO, logger="httpx"): await send_message("check token pattern") info_records = [ r for r in caplog.records if r.name.startswith("httpx") and r.levelno <= logging.INFO ] for record in info_records: assert not BOT_TOKEN_PATTERN.search(record.message), ( f"Паттерн bot[0-9]+: найден в httpx INFO-логе: {record.message!r}" )