kin: BATON-SEC-003-backend_dev

This commit is contained in:
Gros Frumos 2026-03-21 08:12:01 +02:00
parent 097b7af949
commit f17ee79edb
13 changed files with 593 additions and 125 deletions

View file

@ -6,6 +6,9 @@ Regression tests for BATON-SEC-007:
3. POST /api/signal uses asyncio.create_task HTTP response is not blocked
by Telegram rate-limit pauses.
4. GET /health returns only {"status": "ok"} no timestamp field.
BATON-SEC-003: POST /api/signal now requires Authorization: Bearer <api_key>.
Tests that send signals now register first and use the returned api_key.
"""
from __future__ import annotations
@ -18,6 +21,7 @@ os.environ.setdefault("CHAT_ID", "-1001234567890")
os.environ.setdefault("WEBHOOK_SECRET", "test-webhook-secret")
os.environ.setdefault("WEBHOOK_URL", "https://example.com/api/webhook/telegram")
os.environ.setdefault("FRONTEND_ORIGIN", "http://localhost:3000")
os.environ.setdefault("ADMIN_TOKEN", "test-admin-token")
from unittest.mock import AsyncMock, patch
@ -31,6 +35,10 @@ from tests.conftest import make_app_client
SEND_URL = f"https://api.telegram.org/bot{config.BOT_TOKEN}/sendMessage"
# Valid UUID v4 constants
_UUID_CT = "d4e5f6a7-b8c9-4d0e-a1b2-c3d4e5f6a7b8"
_UUID_SLOW = "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9"
# ---------------------------------------------------------------------------
# Criterion 1 — retry loop is bounded to max 3 attempts
@ -164,10 +172,13 @@ async def test_signal_uses_create_task_for_telegram_send_message():
"""POST /api/signal must wrap telegram.send_message in asyncio.create_task."""
with patch("backend.main.asyncio.create_task", wraps=asyncio.create_task) as mock_ct:
async with make_app_client() as client:
await client.post("/api/register", json={"uuid": "d4e5f6a7-b8c9-4d0e-a1b2-c3d4e5f6a7b8", "name": "CT"})
reg = await client.post("/api/register", json={"uuid": _UUID_CT, "name": "CT"})
assert reg.status_code == 200
api_key = reg.json()["api_key"]
resp = await client.post(
"/api/signal",
json={"user_id": "d4e5f6a7-b8c9-4d0e-a1b2-c3d4e5f6a7b8", "timestamp": 1742478000000},
json={"user_id": _UUID_CT, "timestamp": 1742478000000},
headers={"Authorization": f"Bearer {api_key}"},
)
assert resp.status_code == 200
@ -177,8 +188,6 @@ async def test_signal_uses_create_task_for_telegram_send_message():
@pytest.mark.asyncio
async def test_signal_response_returns_before_telegram_completes():
"""POST /api/signal returns 200 even when Telegram send_message is delayed."""
# Simulate a slow Telegram response. If send_message is awaited directly,
# the HTTP response would be delayed until sleep completes.
slow_sleep_called = False
async def slow_send_message(_text: str) -> None:
@ -189,19 +198,21 @@ async def test_signal_response_returns_before_telegram_completes():
with patch("backend.main.telegram.send_message", side_effect=slow_send_message):
with patch("backend.main.asyncio.create_task", wraps=asyncio.create_task):
async with make_app_client() as client:
await client.post(
reg = await client.post(
"/api/register",
json={"uuid": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9", "name": "Slow"},
json={"uuid": _UUID_SLOW, "name": "Slow"},
)
assert reg.status_code == 200
api_key = reg.json()["api_key"]
resp = await client.post(
"/api/signal",
json={
"user_id": "e5f6a7b8-c9d0-4e1f-a2b3-c4d5e6f7a8b9",
"user_id": _UUID_SLOW,
"timestamp": 1742478000000,
},
headers={"Authorization": f"Bearer {api_key}"},
)
# Response must be immediate — no blocking on the 9999-second sleep
assert resp.status_code == 200