Merge branch 'BATON-SEC-007-backend_dev'

This commit is contained in:
Gros Frumos 2026-03-21 07:39:41 +02:00
commit 205cc8037c
3 changed files with 13 additions and 15 deletions

View file

@ -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, _: None = Depends(rate_limit_signal)) -> S
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)

View file

@ -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:

View file

@ -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