buhapp-backend/tests/conftest.py
ga c30faf02ef Sprint 6: security hardening
1. CORS whitelist (was '*'): only buhapp.mygoodservice.ru, t.me, web.telegram.org
2. Rate limit on /auth/login (10/min/IP) and /auth/register (5/hour/IP)
3. TGHandler: removed unused JWTSecret, added WebhookSecret, real secret check
4. Login: constant-time bcrypt on user enumeration (dummy hash)
5. TgUsername saved in users (was lost)
6. User.IsVerified=true for Telegram users
7. Register: 409 instead of 500 on duplicate email
8. Bumped version to 0.4.0
2026-08-20 20:42:22 +00:00

103 lines
3.1 KiB
Python

"""Shared fixtures for BuhApp backend API tests.
Base URL is overridable via the BUHAPP_API env var. Tests register fresh
users on each session — they're not cleaned up, which matches the API's
behaviour (duplicate emails return 409).
"""
from __future__ import annotations
import os
import secrets
import string
import time
import uuid
from typing import Any, Dict
import pytest
import requests
BASE_URL = os.environ.get("BUHAPP_API", "https://api.buhapp.mygoodservice.ru").rstrip("/")
TIMEOUT = 15
# ----------------------------- helpers -----------------------------
def _rand_email() -> str:
"""Random, unique-looking email that is unlikely to collide with real users."""
suffix = "".join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(10))
return f"qa-{suffix}-{int(time.time() * 1000)}@buhapp-test.local"
def _rand_name(prefix: str = "QA") -> str:
return f"{prefix} {secrets.token_hex(3)}"
def consents_block() -> Dict[str, bool]:
return {"adult": True, "terms": True, "privacy": True, "disclaimer": True}
def register_user(name_prefix: str = "QA") -> Dict[str, Any]:
"""Register a fresh user and return {"user": {...}, "tokens": {...}}."""
email = _rand_email()
payload = {
"email": email,
"password": "TestPass123!",
"name": _rand_name(name_prefix),
"city": "Москва",
"gender": "m",
"birthdate": "1995-06-15",
"consents": consents_block(),
}
r = requests.post(f"{BASE_URL}/api/v1/auth/register", json=payload, timeout=TIMEOUT)
assert r.status_code == 201, f"register failed: {r.status_code} {r.text}"
return r.json()
# ----------------------------- fixtures -----------------------------
@pytest.fixture(scope="session")
def base_url() -> str:
return BASE_URL
@pytest.fixture(scope="session")
def api_alive(base_url: str) -> bool:
r = requests.get(f"{base_url}/health", timeout=TIMEOUT)
return r.status_code == 200 and r.json().get("status") == "ok"
@pytest.fixture
def new_user() -> Dict[str, Any]:
"""Freshly registered user with tokens."""
return register_user("UserA")
@pytest.fixture
def second_user() -> Dict[str, Any]:
"""A second freshly registered user, used for chat/block/review flows."""
return register_user("UserB")
@pytest.fixture
def auth_headers(new_user: Dict[str, Any]) -> Dict[str, str]:
return {"Authorization": f"Bearer {new_user['tokens']['Access']}"}
@pytest.fixture
def second_auth_headers(second_user: Dict[str, Any]) -> Dict[str, str]:
return {"Authorization": f"Bearer {second_user['tokens']['Access']}"}
@pytest.fixture
def chat_between(new_user, second_user, auth_headers, second_auth_headers):
"""Returns (chat_id, other_user_id) where auth_headers owns the chat with second_user."""
r = requests.post(
f"{BASE_URL}/api/v1/chats",
json={"other_id": second_user["user"]["id"]},
headers=auth_headers,
timeout=TIMEOUT,
)
assert r.status_code == 200, f"ensure chat failed: {r.status_code} {r.text}"
return r.json()["id"], second_user["user"]["id"]