kin: BATON-SEC-003-backend_dev

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

View file

@ -6,6 +6,9 @@ Acceptance criteria:
5 requests pass (200), 6th returns 429; counter resets after the 10-minute window.
2. Token comparison is timing-safe:
secrets.compare_digest is used in middleware.py (no == / != for token comparison).
UUID notes: RegisterRequest.uuid requires a valid UUID v4 pattern.
All UUID constants below satisfy this constraint.
"""
from __future__ import annotations
@ -20,6 +23,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")
import pytest
from tests.conftest import make_app_client
@ -38,6 +42,24 @@ _SAMPLE_UPDATE = {
},
}
# Valid UUID v4 constants for rate-limit tests
# Pattern: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}
_UUIDS_OK = [
f"d0{i:06d}-0000-4000-8000-000000000001"
for i in range(10)
]
_UUIDS_BLK = [
f"d1{i:06d}-0000-4000-8000-000000000001"
for i in range(10)
]
_UUIDS_EXP = [
f"d2{i:06d}-0000-4000-8000-000000000001"
for i in range(10)
]
_UUID_BLK_999 = "d1000999-0000-4000-8000-000000000001"
_UUID_EXP_BLK = "d2000999-0000-4000-8000-000000000001"
_UUID_EXP_AFTER = "d2001000-0000-4000-8000-000000000001"
# ---------------------------------------------------------------------------
# Criterion 1 — Rate limiting: first 5 requests pass
@ -51,7 +73,7 @@ async def test_register_rate_limit_allows_five_requests():
for i in range(5):
resp = await client.post(
"/api/register",
json={"uuid": f"rl-ok-{i:03d}", "name": f"User{i}"},
json={"uuid": _UUIDS_OK[i], "name": f"User{i}"},
)
assert resp.status_code == 200, (
f"Request {i + 1}/5 unexpectedly returned {resp.status_code}"
@ -70,11 +92,11 @@ async def test_register_rate_limit_blocks_sixth_request():
for i in range(5):
await client.post(
"/api/register",
json={"uuid": f"rl-blk-{i:03d}", "name": f"User{i}"},
json={"uuid": _UUIDS_BLK[i], "name": f"User{i}"},
)
resp = await client.post(
"/api/register",
json={"uuid": "rl-blk-999", "name": "Attacker"},
json={"uuid": _UUID_BLK_999, "name": "Attacker"},
)
assert resp.status_code == 429
@ -94,13 +116,13 @@ async def test_register_rate_limit_resets_after_window_expires():
for i in range(5):
await client.post(
"/api/register",
json={"uuid": f"rl-exp-{i:03d}", "name": f"User{i}"},
json={"uuid": _UUIDS_EXP[i], "name": f"User{i}"},
)
# Verify the 6th is blocked before window expiry
blocked = await client.post(
"/api/register",
json={"uuid": "rl-exp-blk", "name": "Attacker"},
json={"uuid": _UUID_EXP_BLK, "name": "Attacker"},
)
assert blocked.status_code == 429, (
"Expected 429 after exhausting rate limit, got " + str(blocked.status_code)
@ -110,7 +132,7 @@ async def test_register_rate_limit_resets_after_window_expires():
with patch("time.time", return_value=base_time + 601):
resp_after = await client.post(
"/api/register",
json={"uuid": "rl-exp-after", "name": "Legit"},
json={"uuid": _UUID_EXP_AFTER, "name": "Legit"},
)
assert resp_after.status_code == 200, (