125 lines
3.6 KiB
Python
125 lines
3.6 KiB
Python
"""Общие приспособления тестов админки: Telegram-вход и CSRF-токен."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import hmac
|
||
import re
|
||
import time
|
||
from typing import Any
|
||
|
||
import pytest
|
||
from fastapi.testclient import TestClient
|
||
|
||
from admin.app import create_app
|
||
from common.config import AdminSettings
|
||
from common.repositories import admins_repo
|
||
|
||
BOT_TOKEN = "123:TEST"
|
||
SESSION_SECRET = "test-secret"
|
||
ADMIN: dict[str, Any] = {
|
||
"telegram_id": 1,
|
||
"username": "admin",
|
||
"role": "admin",
|
||
"enabled": True,
|
||
"created_at": None,
|
||
"last_login_at": None,
|
||
}
|
||
|
||
_CSRF_RE = re.compile(r'name="csrf_token" value="([^"]+)"')
|
||
|
||
|
||
def admin_settings(**overrides: Any) -> AdminSettings:
|
||
values: dict[str, Any] = {
|
||
"_env_file": None,
|
||
"bot_token": BOT_TOKEN,
|
||
"bot_username": "debot_test_bot",
|
||
"admin_public_url": "https://debot-test.mpakosrv.ru",
|
||
"admin_session_secret": SESSION_SECRET,
|
||
"db_password": "test",
|
||
"app_env": "dev",
|
||
}
|
||
values.update(overrides)
|
||
return AdminSettings(**values)
|
||
|
||
|
||
def telegram_params(
|
||
*,
|
||
token: str = BOT_TOKEN,
|
||
telegram_id: int = 1,
|
||
auth_date: int | None = None,
|
||
) -> dict[str, str]:
|
||
"""Собрать query как от Telegram Login Widget (с валидной подписью)."""
|
||
data = {
|
||
"id": str(telegram_id),
|
||
"first_name": "Admin",
|
||
"username": "admin",
|
||
"auth_date": str(auth_date if auth_date is not None else int(time.time())),
|
||
}
|
||
check_string = "\n".join(f"{key}={value}" for key, value in sorted(data.items()))
|
||
secret = hashlib.sha256(token.encode()).digest()
|
||
data["hash"] = hmac.new(secret, check_string.encode(), hashlib.sha256).hexdigest()
|
||
return data
|
||
|
||
|
||
def csrf_from(html: str) -> str:
|
||
"""Достать CSRF-токен из HTML формы."""
|
||
match = _CSRF_RE.search(html)
|
||
assert match, "в странице нет csrf_token"
|
||
return match.group(1)
|
||
|
||
|
||
@pytest.fixture
|
||
def bot_token() -> str:
|
||
"""Токен тестового бота (подпись Telegram-логина)."""
|
||
return BOT_TOKEN
|
||
|
||
|
||
@pytest.fixture
|
||
def csrf_token():
|
||
"""Функция: достать CSRF-токен из HTML."""
|
||
return csrf_from
|
||
|
||
|
||
@pytest.fixture
|
||
def telegram_query():
|
||
"""Функция: собрать подписанный query Telegram-логина."""
|
||
return telegram_params
|
||
|
||
|
||
@pytest.fixture
|
||
def settings_factory():
|
||
"""Фабрика настроек админки для тестов."""
|
||
return admin_settings
|
||
|
||
|
||
@pytest.fixture
|
||
def client_factory(monkeypatch: pytest.MonkeyPatch):
|
||
"""Фабрика TestClient: подменяет проверку администратора и умеет логиниться."""
|
||
|
||
async def fake_get_admin(telegram_id: int) -> dict[str, Any] | None:
|
||
return ADMIN if int(telegram_id) == ADMIN["telegram_id"] else None
|
||
|
||
async def fake_touch_login(telegram_id: int) -> None:
|
||
return None
|
||
|
||
monkeypatch.setattr(admins_repo, "get_admin", fake_get_admin)
|
||
monkeypatch.setattr(admins_repo, "touch_login", fake_touch_login)
|
||
|
||
def make(
|
||
*,
|
||
authenticated: bool = True,
|
||
follow_redirects: bool = True,
|
||
settings: AdminSettings | None = None,
|
||
) -> TestClient:
|
||
client = TestClient(
|
||
create_app(settings or admin_settings(), init_db=False),
|
||
follow_redirects=follow_redirects,
|
||
)
|
||
if authenticated:
|
||
response = client.get("/auth/telegram", params=telegram_params())
|
||
assert response.status_code in {200, 303}
|
||
return client
|
||
|
||
return make
|