baton/tests/test_fix_011.py

116 lines
5.1 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.

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