From 2d66b1da5889b3bc8690a0a123c85af0c44a2987 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Sat, 21 Mar 2026 08:12:49 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20BATON-007=20=D0=9F=D1=80=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D0=B8=20=D0=BD=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D1=83=20=D0=BF=D1=80=D0=BE=D0=B8=D1=81?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=B8=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D1=82=D0=BE=20=D1=81=D0=B8?= =?UTF-8?q?=D0=B3=D0=BD=D0=B0=D0=BB=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD,=20=D0=BD=D0=BE=20=D0=B2=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B3=D1=80=D0=B0=D0=BC=20=D0=B3=D1=80=D1=83=D0=BF?= =?UTF-8?q?=D0=BF=D1=83=20=D0=BD=D0=B8=D1=87=D0=B5=D0=B3=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B8=D1=85=D0=BE=D0=B4=D0=B8=D1=82.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_telegram.py | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/test_telegram.py b/tests/test_telegram.py index 17ec801..264625b 100644 --- a/tests/test_telegram.py +++ b/tests/test_telegram.py @@ -261,6 +261,71 @@ async def test_aggregator_buffer_cleared_after_flush(): _cleanup(path) +# --------------------------------------------------------------------------- +# BATON-007: 400 "chat not found" handling +# --------------------------------------------------------------------------- + +@pytest.mark.asyncio +async def test_send_message_400_chat_not_found_does_not_raise(): + """400 'chat not found' must not raise an exception (service stays alive).""" + with respx.mock(assert_all_called=False) as mock: + mock.post(SEND_URL).mock( + return_value=httpx.Response( + 400, + json={"ok": False, "error_code": 400, "description": "Bad Request: chat not found"}, + ) + ) + # Must not raise — service must stay alive even with wrong CHAT_ID + await send_message("test") + + +@pytest.mark.asyncio +async def test_send_message_400_chat_not_found_logs_error(caplog): + """400 response from Telegram must be logged as ERROR with the status code.""" + import logging + + with respx.mock(assert_all_called=False) as mock: + mock.post(SEND_URL).mock( + return_value=httpx.Response( + 400, + json={"ok": False, "error_code": 400, "description": "Bad Request: chat not found"}, + ) + ) + with caplog.at_level(logging.ERROR, logger="backend.telegram"): + await send_message("test chat not found") + + assert any("400" in record.message for record in caplog.records), ( + "Expected ERROR log containing '400' but got: " + str([r.message for r in caplog.records]) + ) + + +@pytest.mark.asyncio +async def test_send_message_400_breaks_after_first_attempt(): + """On 400, send_message breaks immediately (no retry loop) — only one HTTP call made.""" + 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("test no retry on 400") + + assert route.call_count == 1, f"Expected 1 call on 400, got {route.call_count}" + + +@pytest.mark.asyncio +async def test_send_message_all_5xx_retries_exhausted_does_not_raise(): + """When all 3 attempts fail with 5xx, send_message logs error but does NOT raise.""" + with respx.mock(assert_all_called=False) as mock: + mock.post(SEND_URL).mock( + return_value=httpx.Response(500, text="Internal Server Error") + ) + with patch("backend.telegram.asyncio.sleep", new_callable=AsyncMock): + # Must not raise — message is dropped, service stays alive + await send_message("test all retries exhausted") + + @pytest.mark.asyncio async def test_aggregator_unknown_user_shows_uuid_prefix(): """If user_name is None, the message shows first 8 chars of uuid."""