diff --git a/backend/main.py b/backend/main.py index 38207f0..0f1dd27 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,7 +4,6 @@ import asyncio import hashlib import logging import os -import time from contextlib import asynccontextmanager from datetime import datetime, timezone from typing import Any @@ -113,7 +112,7 @@ app.add_middleware( @app.get("/health") @app.get("/api/health") async def health() -> dict[str, Any]: - return {"status": "ok", "timestamp": int(time.time())} + return {"status": "ok"} @app.post("/api/register", response_model=RegisterResponse) @@ -153,7 +152,7 @@ async def signal(body: SignalRequest) -> SignalResponse: f"⏰ {ts.strftime('%H:%M:%S')} UTC\n" f"{geo_info}" ) - await telegram.send_message(text) + asyncio.create_task(telegram.send_message(text)) return SignalResponse(status="ok", signal_id=signal_id) diff --git a/backend/telegram.py b/backend/telegram.py index 0436dea..b7018e9 100644 --- a/backend/telegram.py +++ b/backend/telegram.py @@ -17,24 +17,23 @@ _TELEGRAM_API = "https://api.telegram.org/bot{token}/{method}" async def send_message(text: str) -> None: url = _TELEGRAM_API.format(token=config.BOT_TOKEN, method="sendMessage") async with httpx.AsyncClient(timeout=10) as client: - while True: + for attempt in range(3): resp = await client.post(url, json={"chat_id": config.CHAT_ID, "text": text}) if resp.status_code == 429: retry_after = resp.json().get("parameters", {}).get("retry_after", 30) - logger.warning("Telegram 429, sleeping %s sec", retry_after) - await asyncio.sleep(retry_after) + sleep = retry_after * (attempt + 1) + logger.warning("Telegram 429, sleeping %s sec (attempt %d)", sleep, attempt + 1) + await asyncio.sleep(sleep) continue if resp.status_code >= 500: logger.error("Telegram 5xx: %s", resp.text) await asyncio.sleep(30) - resp2 = await client.post( - url, json={"chat_id": config.CHAT_ID, "text": text} - ) - if resp2.status_code != 200: - logger.error("Telegram retry failed: %s", resp2.text) + continue elif resp.status_code != 200: logger.error("Telegram error %s: %s", resp.status_code, resp.text) break + else: + logger.error("Telegram send_message: all 3 attempts failed, message dropped") async def set_webhook(url: str, secret: str) -> None: diff --git a/tests/test_arch_013.py b/tests/test_arch_013.py index b70c682..307ffbc 100644 --- a/tests/test_arch_013.py +++ b/tests/test_arch_013.py @@ -54,14 +54,14 @@ async def test_health_returns_status_ok(): @pytest.mark.asyncio -async def test_health_returns_timestamp(): - """GET /health должен вернуть поле timestamp в JSON.""" +async def test_health_no_timestamp(): + """GET /health не должен возвращать поле timestamp (устраняет time-based fingerprinting).""" async with make_app_client() as client: response = await client.get("/health") data = response.json() - assert "timestamp" in data - assert isinstance(data["timestamp"], int) + assert "timestamp" not in data + assert data == {"status": "ok"} @pytest.mark.asyncio