kin: BATON-007 При нажатии на кнопку происходит анимация и сообщение что сигнал отправлен, но в телеграм группу ничего не приходит.
This commit is contained in:
parent
726bb0a82c
commit
e21bcb1eb4
1 changed files with 121 additions and 2 deletions
|
|
@ -15,6 +15,7 @@ Physical delivery to an actual Telegram group is outside unit test scope.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ.setdefault("BOT_TOKEN", "test-bot-token")
|
os.environ.setdefault("BOT_TOKEN", "test-bot-token")
|
||||||
|
|
@ -30,9 +31,9 @@ from unittest.mock import AsyncMock, patch
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
import respx
|
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
|
# Valid UUID v4 constants — must not collide with UUIDs in other test files
|
||||||
_UUID_A = "d0000001-0000-4000-8000-000000000001"
|
_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_C = "d0000003-0000-4000-8000-000000000003"
|
||||||
_UUID_D = "d0000004-0000-4000-8000-000000000004"
|
_UUID_D = "d0000004-0000-4000-8000-000000000004"
|
||||||
_UUID_E = "d0000005-0000-4000-8000-000000000005"
|
_UUID_E = "d0000005-0000-4000-8000-000000000005"
|
||||||
|
_UUID_F = "d0000006-0000-4000-8000-000000000006"
|
||||||
|
|
||||||
|
|
||||||
async def _register(client: AsyncClient, uuid: str, name: str) -> str:
|
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"], (
|
assert r2.json()["signal_id"] > r1.json()["signal_id"], (
|
||||||
"Second signal must have a higher signal_id than the first"
|
"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."
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue