116 lines
5.1 KiB
Python
116 lines
5.1 KiB
Python
"""
|
||
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}"
|
||
)
|