109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
"""Тесты входа в админку через Telegram: подпись, сессия и CSRF."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
|
||
from fastapi.testclient import TestClient
|
||
|
||
from admin.security import verify_telegram_auth
|
||
|
||
|
||
def test_healthz_is_public(client_factory) -> None:
|
||
assert client_factory(authenticated=False).get("/healthz").status_code == 200
|
||
|
||
|
||
def test_protected_page_redirects_to_login(client_factory) -> None:
|
||
response = client_factory(authenticated=False, follow_redirects=False).get("/")
|
||
|
||
assert response.status_code == 303
|
||
assert response.headers["location"].startswith("/login")
|
||
|
||
|
||
def test_login_page_has_widget(client_factory) -> None:
|
||
response = client_factory(authenticated=False).get("/login")
|
||
|
||
assert response.status_code == 200
|
||
assert "telegram-widget.js" in response.text
|
||
assert "debot_test_bot" in response.text
|
||
assert "https://debot-test.mpakosrv.ru/auth/telegram" in response.text
|
||
|
||
|
||
def test_login_page_without_config(client_factory, settings_factory) -> None:
|
||
client = client_factory(
|
||
authenticated=False,
|
||
settings=settings_factory(bot_token="", bot_username=""),
|
||
)
|
||
|
||
response = client.get("/login")
|
||
|
||
assert response.status_code == 200
|
||
assert "Вход не настроен" in response.text
|
||
|
||
|
||
def test_login_with_valid_signature(client_factory, telegram_query) -> None:
|
||
client: TestClient = client_factory(authenticated=False, follow_redirects=False)
|
||
|
||
response = client.get("/auth/telegram", params=telegram_query())
|
||
|
||
assert response.status_code == 303
|
||
assert response.headers["location"] == "/"
|
||
assert client.get("/").status_code == 200
|
||
|
||
|
||
def test_login_rejects_bad_hash(client_factory, telegram_query) -> None:
|
||
params = telegram_query()
|
||
params["hash"] = "0" * 64
|
||
|
||
response = client_factory(authenticated=False).get("/auth/telegram", params=params)
|
||
|
||
assert response.status_code == 403
|
||
assert "Не удалось проверить вход" in response.text
|
||
|
||
|
||
def test_login_rejects_stale_auth_date(client_factory, telegram_query) -> None:
|
||
params = telegram_query(auth_date=int(time.time()) - 90_000)
|
||
|
||
response = client_factory(authenticated=False).get("/auth/telegram", params=params)
|
||
|
||
assert response.status_code == 403
|
||
|
||
|
||
def test_login_rejects_non_admin(client_factory, telegram_query) -> None:
|
||
response = client_factory(authenticated=False).get("/auth/telegram", params=telegram_query(telegram_id=2))
|
||
|
||
assert response.status_code == 403
|
||
assert "нет доступа" in response.text
|
||
|
||
|
||
def test_logout_requires_csrf(client_factory) -> None:
|
||
response = client_factory().post("/logout")
|
||
|
||
assert response.status_code == 400
|
||
|
||
|
||
def test_logout_clears_session(client_factory, csrf_token) -> None:
|
||
client: TestClient = client_factory()
|
||
token = csrf_token(client.get("/").text)
|
||
|
||
response = client.post("/logout", data={"csrf_token": token})
|
||
|
||
assert response.status_code == 200
|
||
assert client.get("/", follow_redirects=False).status_code == 303
|
||
|
||
|
||
def test_verify_signature(bot_token: str) -> None:
|
||
"""Юнит-проверка подписи без HTTP (данные собираем тем же алгоритмом)."""
|
||
import hashlib
|
||
import hmac
|
||
|
||
data = {"id": "1", "first_name": "Admin", "auth_date": str(int(time.time()))}
|
||
check = "\n".join(f"{key}={value}" for key, value in sorted(data.items()))
|
||
secret = hashlib.sha256(bot_token.encode()).digest()
|
||
data["hash"] = hmac.new(secret, check.encode(), hashlib.sha256).hexdigest()
|
||
|
||
assert verify_telegram_auth(data, bot_token)
|
||
assert not verify_telegram_auth({**data, "id": "2"}, bot_token)
|
||
assert not verify_telegram_auth(data, "999:OTHER")
|
||
assert not verify_telegram_auth(data, bot_token, now=time.time() + 90_000)
|