kin: BATON-SEC-007-backend_dev

This commit is contained in:
Gros Frumos 2026-03-21 07:39:41 +02:00
parent 3483b71fcb
commit 1cdd1e15da
3 changed files with 13 additions and 15 deletions

View file

@ -4,7 +4,6 @@ import asyncio
import hashlib import hashlib
import logging import logging
import os import os
import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any
@ -113,7 +112,7 @@ app.add_middleware(
@app.get("/health") @app.get("/health")
@app.get("/api/health") @app.get("/api/health")
async def health() -> dict[str, Any]: async def health() -> dict[str, Any]:
return {"status": "ok", "timestamp": int(time.time())} return {"status": "ok"}
@app.post("/api/register", response_model=RegisterResponse) @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"{ts.strftime('%H:%M:%S')} UTC\n"
f"{geo_info}" f"{geo_info}"
) )
await telegram.send_message(text) asyncio.create_task(telegram.send_message(text))
return SignalResponse(status="ok", signal_id=signal_id) return SignalResponse(status="ok", signal_id=signal_id)

View file

@ -17,24 +17,23 @@ _TELEGRAM_API = "https://api.telegram.org/bot{token}/{method}"
async def send_message(text: str) -> None: async def send_message(text: str) -> None:
url = _TELEGRAM_API.format(token=config.BOT_TOKEN, method="sendMessage") url = _TELEGRAM_API.format(token=config.BOT_TOKEN, method="sendMessage")
async with httpx.AsyncClient(timeout=10) as client: 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}) resp = await client.post(url, json={"chat_id": config.CHAT_ID, "text": text})
if resp.status_code == 429: if resp.status_code == 429:
retry_after = resp.json().get("parameters", {}).get("retry_after", 30) retry_after = resp.json().get("parameters", {}).get("retry_after", 30)
logger.warning("Telegram 429, sleeping %s sec", retry_after) sleep = retry_after * (attempt + 1)
await asyncio.sleep(retry_after) logger.warning("Telegram 429, sleeping %s sec (attempt %d)", sleep, attempt + 1)
await asyncio.sleep(sleep)
continue continue
if resp.status_code >= 500: if resp.status_code >= 500:
logger.error("Telegram 5xx: %s", resp.text) logger.error("Telegram 5xx: %s", resp.text)
await asyncio.sleep(30) await asyncio.sleep(30)
resp2 = await client.post( continue
url, json={"chat_id": config.CHAT_ID, "text": text}
)
if resp2.status_code != 200:
logger.error("Telegram retry failed: %s", resp2.text)
elif resp.status_code != 200: elif resp.status_code != 200:
logger.error("Telegram error %s: %s", resp.status_code, resp.text) logger.error("Telegram error %s: %s", resp.status_code, resp.text)
break break
else:
logger.error("Telegram send_message: all 3 attempts failed, message dropped")
async def set_webhook(url: str, secret: str) -> None: async def set_webhook(url: str, secret: str) -> None:

View file

@ -54,14 +54,14 @@ async def test_health_returns_status_ok():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_health_returns_timestamp(): async def test_health_no_timestamp():
"""GET /health должен вернуть поле timestamp в JSON.""" """GET /health не должен возвращать поле timestamp (устраняет time-based fingerprinting)."""
async with make_app_client() as client: async with make_app_client() as client:
response = await client.get("/health") response = await client.get("/health")
data = response.json() data = response.json()
assert "timestamp" in data assert "timestamp" not in data
assert isinstance(data["timestamp"], int) assert data == {"status": "ok"}
@pytest.mark.asyncio @pytest.mark.asyncio