fix(BATON-007): add validate_bot_token() for startup detection and fix test mocks

- Add validate_bot_token() to backend/telegram.py: calls getMe on startup,
  logs ERROR if token is invalid (never raises per #1215 contract)
- Call validate_bot_token() in lifespan() after db.init_db() for early detection
- Update conftest.py make_app_client() to mock getMe endpoint
- Add 3 tests for validate_bot_token (200, 401, network error cases)

Root cause: CHAT_ID=5190015988 (positive) was wrong — fixed to -5190015988
on server per decision #1212. Group "Big Red Button" confirmed via getChat.
Service restarted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gros Frumos 2026-03-21 08:54:07 +02:00
parent 8ee9782737
commit 51f1943c55
4 changed files with 75 additions and 1 deletions

View file

@ -79,6 +79,7 @@ def make_app_client():
"""
tg_set_url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/setWebhook"
send_url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendMessage"
get_me_url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/getMe"
@contextlib.asynccontextmanager
async def _ctx():
@ -86,6 +87,9 @@ def make_app_client():
from backend.main import app
mock_router = respx.mock(assert_all_called=False)
mock_router.get(get_me_url).mock(
return_value=httpx.Response(200, json={"ok": True, "result": {"username": "testbot"}})
)
mock_router.post(tg_set_url).mock(
return_value=httpx.Response(200, json={"ok": True, "result": True})
)

View file

@ -34,11 +34,57 @@ import pytest
import respx
from backend import config
from backend.telegram import SignalAggregator, send_message, set_webhook
from backend.telegram import SignalAggregator, send_message, set_webhook, validate_bot_token
SEND_URL = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendMessage"
WEBHOOK_URL_API = f"https://api.telegram.org/bot{config.BOT_TOKEN}/setWebhook"
GET_ME_URL = f"https://api.telegram.org/bot{config.BOT_TOKEN}/getMe"
# ---------------------------------------------------------------------------
# validate_bot_token
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_validate_bot_token_returns_true_on_200():
"""validate_bot_token returns True when getMe responds 200."""
with respx.mock(assert_all_called=False) as mock:
mock.get(GET_ME_URL).mock(
return_value=httpx.Response(200, json={"ok": True, "result": {"username": "batonbot"}})
)
result = await validate_bot_token()
assert result is True
@pytest.mark.asyncio
async def test_validate_bot_token_returns_false_on_401(caplog):
"""validate_bot_token returns False and logs ERROR when getMe responds 401."""
import logging
with respx.mock(assert_all_called=False) as mock:
mock.get(GET_ME_URL).mock(
return_value=httpx.Response(401, json={"ok": False, "description": "Unauthorized"})
)
with caplog.at_level(logging.ERROR, logger="backend.telegram"):
result = await validate_bot_token()
assert result is False
assert any("401" in record.message for record in caplog.records)
@pytest.mark.asyncio
async def test_validate_bot_token_returns_false_on_network_error(caplog):
"""validate_bot_token returns False and logs ERROR on network failure — never raises."""
import logging
with respx.mock(assert_all_called=False) as mock:
mock.get(GET_ME_URL).mock(side_effect=httpx.ConnectError("connection refused"))
with caplog.at_level(logging.ERROR, logger="backend.telegram"):
result = await validate_bot_token()
assert result is False
assert len(caplog.records) >= 1
# ---------------------------------------------------------------------------