kin: BATON-FIX-011 Скрыть BOT_TOKEN из httpx/journalctl логов
This commit is contained in:
parent
42f4251184
commit
2ab5e9ab54
2 changed files with 416 additions and 0 deletions
116
tests/test_fix_011.py
Normal file
116
tests/test_fix_011.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"""
|
||||
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}"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue