baton/tests/test_fix_011.py

117 lines
5.1 KiB
Python
Raw Normal View History

"""
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}"
)