Files
ForbiddenStarsApp/backend/tests/test_profile.py
T

127 lines
4.8 KiB
Python

"""Профиль: «о себе» (bio), аватар (загрузка/отдача/удаление), публичный профиль."""
from __future__ import annotations
from fastapi.testclient import TestClient
from tests.conftest import add_group_member, create_finished_match, csrf_headers, login
# Минимальный «PNG»: достаточно сигнатуры — сервер не декодирует, только сниффит тип.
PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 64
def _use_tmp_uploads(monkeypatch, tmp_path) -> None:
from app.core.config import settings
monkeypatch.setattr(settings, "dev_upload_dir", str(tmp_path))
def _finished_match_for(client: TestClient, engine, me: dict) -> int:
"""Группа + второй игрок + одна завершённая партия (чтобы me попал в лидерборд)."""
exps = [e["id"] for e in client.get("/api/expansions").json()]
gid = client.post(
"/api/groups", json={"name": "Группа", "expansion_ids": exps}, headers=csrf_headers(client)
).json()["id"]
p2 = add_group_member(engine, gid, "Соперник")
fids = [f["id"] for f in client.get(f"/api/groups/{gid}/factions").json()]
create_finished_match(
client,
gid,
[
{"user_id": me["id"], "faction_id": fids[0], "place": 1},
{"user_id": p2, "faction_id": fids[1], "place": 2},
],
)
return gid
def test_update_bio(client: TestClient):
login(client, "Игрок")
r = client.patch("/api/users/me/profile", json={"bio": "Люблю орков"}, headers=csrf_headers(client))
assert r.status_code == 200, r.text
assert r.json()["bio"] == "Люблю орков"
# Слишком длинное «о себе» → 422.
r = client.patch(
"/api/users/me/profile", json={"bio": "x" * 501}, headers=csrf_headers(client)
)
assert r.status_code == 422, r.text
def test_avatar_upload_get_delete(client: TestClient, monkeypatch, tmp_path):
_use_tmp_uploads(monkeypatch, tmp_path)
me = login(client, "Аватарыч")
# Загрузка валидного PNG → avatar_url выставлен.
r = client.put(
"/api/users/me/avatar",
files={"file": ("a.png", PNG, "image/png")},
headers=csrf_headers(client),
)
assert r.status_code == 200, r.text
avatar_url = r.json()["avatar_url"]
assert avatar_url and "/avatar?v=" in avatar_url
# Отдача файла.
g = client.get(f"/api/users/{me['id']}/avatar")
assert g.status_code == 200, g.text
assert g.content == PNG
assert g.headers["content-type"] == "image/png"
# Удаление → avatar_url пропадает, файл больше не отдаётся.
r = client.delete("/api/users/me/avatar", headers=csrf_headers(client))
assert r.status_code == 200 and r.json()["avatar_url"] is None, r.text
assert client.get(f"/api/users/{me['id']}/avatar").status_code == 404
def test_avatar_rejects_non_image_and_oversize(client: TestClient, monkeypatch, tmp_path):
_use_tmp_uploads(monkeypatch, tmp_path)
login(client, "Игрок")
r = client.put(
"/api/users/me/avatar",
files={"file": ("x.txt", b"not an image", "text/plain")},
headers=csrf_headers(client),
)
assert r.status_code == 422, r.text
big = PNG + b"\x00" * (2 * 1024 * 1024 + 10)
r = client.put(
"/api/users/me/avatar",
files={"file": ("big.png", big, "image/png")},
headers=csrf_headers(client),
)
assert r.status_code == 422, r.text
def test_public_profile(client: TestClient):
me = login(client, "Публичный")
client.patch("/api/users/me/profile", json={"bio": "Привет"}, headers=csrf_headers(client))
r = client.get(f"/api/users/{me['id']}/profile")
assert r.status_code == 200, r.text
body = r.json()
assert body["nickname"] == "Публичный"
assert body["bio"] == "Привет"
assert body["avatar_url"] is None
assert "overall" in body["stats"]
assert client.get("/api/users/999999/profile").status_code == 404
def test_leaderboard_includes_avatar_url(client: TestClient, engine, monkeypatch, tmp_path):
_use_tmp_uploads(monkeypatch, tmp_path)
me = login(client, "Топчик")
_finished_match_for(client, engine, me)
def entry_for(uid: int) -> dict:
board = client.get("/api/stats/leaderboard").json()
return next(e for e in board["entries"] + board["provisional"] if e["user_id"] == uid)
assert entry_for(me["id"])["avatar_url"] is None # без аватара — null
client.put(
"/api/users/me/avatar",
files={"file": ("a.png", PNG, "image/png")},
headers=csrf_headers(client),
)
assert "/avatar?v=" in entry_for(me["id"])["avatar_url"] # после загрузки — ссылка