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

@ -9,6 +9,10 @@ Acceptance criteria:
5. Удаление пользователь исчезает из GET /admin/users, возвращается 204
6. Защита: неавторизованный запрос к /admin/* возвращает 401
7. Отсутствие регрессии с основным функционалом
BATON-SEC-003: POST /api/signal now requires Authorization: Bearer <api_key>.
Tests 3 and 4 (block/unblock + signal) use /api/register to obtain an api_key,
then admin block/unblock the user by their DB id.
"""
from __future__ import annotations
@ -33,6 +37,11 @@ NGINX_CONF = PROJECT_ROOT / "nginx" / "baton.conf"
ADMIN_HEADERS = {"Authorization": "Bearer test-admin-token"}
WRONG_HEADERS = {"Authorization": "Bearer wrong-token"}
# Valid UUID v4 for signal-related tests (registered via /api/register)
_UUID_BLOCK = "f0000001-0000-4000-8000-000000000001"
_UUID_UNBLOCK = "f0000002-0000-4000-8000-000000000002"
_UUID_SIG_OK = "f0000003-0000-4000-8000-000000000003"
# ---------------------------------------------------------------------------
# Criterion 6 — Unauthorised requests to /admin/* return 401
@ -250,23 +259,32 @@ async def test_admin_block_user_returns_is_blocked_true() -> None:
async def test_admin_block_user_prevents_signal() -> None:
"""Заблокированный пользователь не может отправить сигнал — /api/signal возвращает 403."""
async with make_app_client() as client:
create_resp = await client.post(
"/admin/users",
json={"uuid": "block-uuid-002", "name": "BlockSignalUser"},
headers=ADMIN_HEADERS,
# Регистрируем через /api/register чтобы получить api_key
reg_resp = await client.post(
"/api/register",
json={"uuid": _UUID_BLOCK, "name": "BlockSignalUser"},
)
user_id = create_resp.json()["id"]
user_uuid = create_resp.json()["uuid"]
assert reg_resp.status_code == 200
api_key = reg_resp.json()["api_key"]
user_uuid = reg_resp.json()["uuid"]
# Находим ID пользователя
users_resp = await client.get("/admin/users", headers=ADMIN_HEADERS)
user = next(u for u in users_resp.json() if u["uuid"] == user_uuid)
user_id = user["id"]
# Блокируем
await client.put(
f"/admin/users/{user_id}/block",
json={"is_blocked": True},
headers=ADMIN_HEADERS,
)
# Заблокированный пользователь должен получить 403
signal_resp = await client.post(
"/api/signal",
json={"user_id": user_uuid, "timestamp": 1700000000000, "geo": None},
headers={"Authorization": f"Bearer {api_key}"},
)
assert signal_resp.status_code == 403
@ -318,13 +336,19 @@ async def test_admin_unblock_user_returns_is_blocked_false() -> None:
async def test_admin_unblock_user_restores_signal_access() -> None:
"""После разблокировки пользователь снова может отправить сигнал (200)."""
async with make_app_client() as client:
create_resp = await client.post(
"/admin/users",
json={"uuid": "unblock-uuid-002", "name": "UnblockSignalUser"},
headers=ADMIN_HEADERS,
# Регистрируем через /api/register чтобы получить api_key
reg_resp = await client.post(
"/api/register",
json={"uuid": _UUID_UNBLOCK, "name": "UnblockSignalUser"},
)
user_id = create_resp.json()["id"]
user_uuid = create_resp.json()["uuid"]
assert reg_resp.status_code == 200
api_key = reg_resp.json()["api_key"]
user_uuid = reg_resp.json()["uuid"]
# Находим ID
users_resp = await client.get("/admin/users", headers=ADMIN_HEADERS)
user = next(u for u in users_resp.json() if u["uuid"] == user_uuid)
user_id = user["id"]
# Блокируем
await client.put(
@ -344,6 +368,7 @@ async def test_admin_unblock_user_restores_signal_access() -> None:
signal_resp = await client.post(
"/api/signal",
json={"user_id": user_uuid, "timestamp": 1700000000000, "geo": None},
headers={"Authorization": f"Bearer {api_key}"},
)
assert signal_resp.status_code == 200
assert signal_resp.json()["status"] == "ok"
@ -462,26 +487,28 @@ async def test_register_not_broken_after_admin_operations() -> None:
# Основной функционал
resp = await client.post(
"/api/register",
json={"uuid": "regress-user-uuid-001", "name": "RegularUser"},
json={"uuid": _UUID_SIG_OK, "name": "RegularUser"},
)
assert resp.status_code == 200
assert resp.json()["uuid"] == "regress-user-uuid-001"
assert resp.json()["uuid"] == _UUID_SIG_OK
@pytest.mark.asyncio
async def test_signal_from_unblocked_user_succeeds() -> None:
"""Незаблокированный пользователь, созданный через admin API, может отправить сигнал."""
async def test_signal_from_registered_unblocked_user_succeeds() -> None:
"""Зарегистрированный незаблокированный пользователь может отправить сигнал."""
async with make_app_client() as client:
create_resp = await client.post(
"/admin/users",
json={"uuid": "regress-signal-uuid-001", "name": "SignalUser"},
headers=ADMIN_HEADERS,
reg_resp = await client.post(
"/api/register",
json={"uuid": _UUID_SIG_OK, "name": "SignalUser"},
)
user_uuid = create_resp.json()["uuid"]
assert reg_resp.status_code == 200
api_key = reg_resp.json()["api_key"]
user_uuid = reg_resp.json()["uuid"]
signal_resp = await client.post(
"/api/signal",
json={"user_id": user_uuid, "timestamp": 1700000000000, "geo": None},
headers={"Authorization": f"Bearer {api_key}"},
)
assert signal_resp.status_code == 200
assert signal_resp.json()["status"] == "ok"