kin: BATON-007 При нажатии на кнопку происходит анимация и сообщение что сигнал отправлен, но в телеграм группу ничего не приходит.

This commit is contained in:
Gros Frumos 2026-03-21 09:05:43 +02:00
parent 726bb0a82c
commit e21bcb1eb4

View file

@ -15,6 +15,7 @@ Physical delivery to an actual Telegram group is outside unit test scope.
from __future__ import annotations
import asyncio
import logging
import os
os.environ.setdefault("BOT_TOKEN", "test-bot-token")
@ -30,9 +31,9 @@ from unittest.mock import AsyncMock, patch
import httpx
import pytest
import respx
from httpx import AsyncClient
from httpx import AsyncClient, ASGITransport
from tests.conftest import make_app_client
from tests.conftest import make_app_client, temp_db
# Valid UUID v4 constants — must not collide with UUIDs in other test files
_UUID_A = "d0000001-0000-4000-8000-000000000001"
@ -40,6 +41,7 @@ _UUID_B = "d0000002-0000-4000-8000-000000000002"
_UUID_C = "d0000003-0000-4000-8000-000000000003"
_UUID_D = "d0000004-0000-4000-8000-000000000004"
_UUID_E = "d0000005-0000-4000-8000-000000000005"
_UUID_F = "d0000006-0000-4000-8000-000000000006"
async def _register(client: AsyncClient, uuid: str, name: str) -> str:
@ -260,3 +262,120 @@ async def test_repeated_signals_produce_incrementing_signal_ids():
assert r2.json()["signal_id"] > r1.json()["signal_id"], (
"Second signal must have a higher signal_id than the first"
)
# ---------------------------------------------------------------------------
# Director revision: regression #1214, #1226
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_send_message_uses_negative_chat_id_from_config():
"""Regression #1226: send_message must POST to Telegram with a negative chat_id.
Root cause of BATON-007: CHAT_ID=5190015988 (positive = user ID) was set in .env
instead of -5190015988 (negative = group ID). This test inspects the actual
chat_id value in the HTTP request body not just call_count.
"""
from backend import config as _cfg
from backend.telegram import send_message
send_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/sendMessage"
with respx.mock(assert_all_called=False) as mock:
route = mock.post(send_url).mock(
return_value=httpx.Response(200, json={"ok": True})
)
await send_message("regression #1226")
assert route.called
body = json.loads(route.calls[0].request.content)
chat_id = body["chat_id"]
assert chat_id == _cfg.CHAT_ID, (
f"Expected chat_id={_cfg.CHAT_ID!r}, got {chat_id!r}"
)
assert str(chat_id).startswith("-"), (
f"Regression #1226: chat_id must be negative (group ID), got: {chat_id!r}. "
"Positive chat_id is a user ID, not a Telegram group."
)
@pytest.mark.asyncio
async def test_send_message_4xx_does_not_trigger_retry_loop():
"""Regression #1214: on Telegram 4xx (wrong chat_id), retry loop must NOT run.
Only one HTTP call should be made. Retrying a 4xx is pointless it will
keep failing. send_message must break immediately on any 4xx response.
"""
from backend import config as _cfg
from backend.telegram import send_message
send_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/sendMessage"
with respx.mock(assert_all_called=False) as mock:
route = mock.post(send_url).mock(
return_value=httpx.Response(
400,
json={"ok": False, "error_code": 400, "description": "Bad Request: chat not found"},
)
)
await send_message("retry test #1214")
assert route.call_count == 1, (
f"Regression #1214: expected exactly 1 HTTP call on 4xx, got {route.call_count}. "
"send_message must break immediately on client errors — no retry loop."
)
@pytest.mark.asyncio
async def test_signal_endpoint_returns_200_on_telegram_4xx(caplog):
"""Regression: /api/signal must return 200 even when Telegram Bot API returns 4xx.
When CHAT_ID is wrong (or any Telegram 4xx), the error must be logged by
send_message but the /api/signal endpoint must still return 200 the signal
was saved to DB, only the Telegram notification failed.
"""
from backend import config as _cfg
from backend.main import app
send_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/sendMessage"
tg_set_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/setWebhook"
get_me_url = f"https://api.telegram.org/bot{_cfg.BOT_TOKEN}/getMe"
with temp_db():
with respx.mock(assert_all_called=False) as mock_tg:
mock_tg.get(get_me_url).mock(
return_value=httpx.Response(200, json={"ok": True, "result": {"username": "testbot"}})
)
mock_tg.post(tg_set_url).mock(
return_value=httpx.Response(200, json={"ok": True, "result": True})
)
mock_tg.post(send_url).mock(
return_value=httpx.Response(
400,
json={"ok": False, "error_code": 400, "description": "Bad Request: chat not found"},
)
)
async with app.router.lifespan_context(app):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
reg = await client.post("/api/register", json={"uuid": _UUID_F, "name": "Tg4xxUser"})
assert reg.status_code == 200, f"Register failed: {reg.text}"
api_key = reg.json()["api_key"]
with caplog.at_level(logging.ERROR, logger="backend.telegram"):
resp = await client.post(
"/api/signal",
json={"user_id": _UUID_F, "timestamp": 1742478000000},
headers={"Authorization": f"Bearer {api_key}"},
)
await asyncio.sleep(0)
assert resp.status_code == 200, (
f"Expected /api/signal to return 200 even when Telegram returns 4xx, got {resp.status_code}"
)
assert any("400" in r.message for r in caplog.records), (
"Expected ERROR log containing '400' when Telegram returns 4xx. "
"Error must be logged, not silently swallowed."
)