"""Общие приспособления тестов админки: 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