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
103 lines
3.1 KiB
Python
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"]
|