Профиль: любимая фракция — выбор игрока, а не вывод из статистики
Раньше «любимая» вычислялась как самая частая среди партий с ручным выбором фракции. Теперь это поле профиля: users.favorite_faction_id (FK на factions, ON DELETE SET NULL, миграция 0010) и необязательное поле в PATCH /api/users/me/profile. Роутер профиля переведён на model_dump(exclude_unset=True) и обновляет только пришедшие поля — иначе сохранение одного «о себе» обнуляло бы выбранную фракцию. «Основная фракция» логику не меняет (она и так считалась по всем партиям, включая рандомные раздачи) и переезжает в подпись «Чаще всего играет на»: FactionStat получает name_ru_prepositional из словаря предложного падежа в faction_service. Словарь в коде, а не в БД: набор фракций задан игрой; фракция вне словаря выводится в именительном. Служебное chosen_games существовало только ради старой «любимой» — убрано из SQL вместе с ней. #17 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""Профиль: «о себе» (bio), аватар (загрузка/отдача/удаление), публичный профиль."""
|
||||
"""Профиль: «о себе» (bio), любимая фракция, аватар (загрузка/отдача/удаление),
|
||||
публичный профиль."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -126,8 +127,8 @@ def test_leaderboard_includes_avatar_url(client: TestClient, engine, monkeypatch
|
||||
assert "/avatar?v=" in entry_for(me["id"])["avatar_url"] # после загрузки — ссылка
|
||||
|
||||
|
||||
def test_most_played_faction_ignores_random(client: TestClient, engine):
|
||||
"""Любимая фракция — только по сознательному выбору: рандомные раздачи не в счёт."""
|
||||
def test_main_faction_counts_random_games(client: TestClient, engine):
|
||||
"""«Чаще всего играет на» — по всей истории, включая рандомные раздачи."""
|
||||
me = login(client, "Фракционер")
|
||||
exps = [e["id"] for e in client.get("/api/expansions").json()]
|
||||
gid = client.post(
|
||||
@@ -156,17 +157,15 @@ def test_most_played_faction_ignores_random(client: TestClient, engine):
|
||||
)
|
||||
|
||||
stats = client.get("/api/users/me/stats").json()
|
||||
assert stats["most_played_faction"]["faction_id"] == fids[2] # B, хоть партий с A больше
|
||||
assert stats["main_faction"]["faction_id"] == fids[0] # основная — по всем партиям
|
||||
|
||||
# У соперника все фракции выбраны вручную — любимая считается как раньше.
|
||||
login(client, "Партнёр")
|
||||
stats2 = client.get("/api/users/me/stats").json()
|
||||
assert stats2["most_played_faction"]["faction_id"] == fids[1]
|
||||
# Условия выбора не важны: A сыграна чаще, пусть и по рандому.
|
||||
assert stats["main_faction"]["faction_id"] == fids[0]
|
||||
assert stats["main_faction"]["name_ru_prepositional"] # падежная форма проставлена
|
||||
# Любимая больше не выводится из истории — она задаётся игроком вручную.
|
||||
assert stats["favorite_faction"] is None
|
||||
|
||||
|
||||
def test_most_played_faction_absent_when_all_random(client: TestClient, engine):
|
||||
"""Если игрок ни разу не выбирал фракцию сам — любимой нет вовсе."""
|
||||
def test_main_faction_present_for_random_only_player(client: TestClient, engine):
|
||||
"""Игроку, который всегда играет рандомом, «чаще всего играет на» всё равно считается."""
|
||||
me = login(client, "Рандомщик")
|
||||
exps = [e["id"] for e in client.get("/api/expansions").json()]
|
||||
gid = client.post(
|
||||
@@ -183,5 +182,61 @@ def test_most_played_faction_absent_when_all_random(client: TestClient, engine):
|
||||
],
|
||||
)
|
||||
stats = client.get("/api/users/me/stats").json()
|
||||
assert stats["most_played_faction"] is None
|
||||
assert stats["main_faction"]["faction_id"] == fids[0] # основная есть и у рандомщика
|
||||
assert stats["main_faction"]["faction_id"] == fids[0]
|
||||
|
||||
|
||||
def test_favorite_faction_set_and_clear(client: TestClient):
|
||||
"""Любимая фракция — личный выбор игрока: сохраняется и снимается."""
|
||||
login(client, "Выбирающий")
|
||||
fid = client.get("/api/factions").json()[0]["id"]
|
||||
|
||||
r = client.patch(
|
||||
"/api/users/me/profile", json={"favorite_faction_id": fid}, headers=csrf_headers(client)
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["favorite_faction_id"] == fid
|
||||
assert client.get("/api/users/me").json()["favorite_faction_id"] == fid
|
||||
# Партий нет вовсе, а любимая фракция всё равно показывается — она не из статистики.
|
||||
assert client.get("/api/users/me/stats").json()["favorite_faction"]["id"] == fid
|
||||
|
||||
r = client.patch(
|
||||
"/api/users/me/profile", json={"favorite_faction_id": None}, headers=csrf_headers(client)
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["favorite_faction_id"] is None
|
||||
assert client.get("/api/users/me/stats").json()["favorite_faction"] is None
|
||||
|
||||
|
||||
def test_favorite_faction_rejects_unknown(client: TestClient):
|
||||
login(client, "Фантазёр")
|
||||
r = client.patch(
|
||||
"/api/users/me/profile", json={"favorite_faction_id": 99999}, headers=csrf_headers(client)
|
||||
)
|
||||
assert r.status_code == 422
|
||||
|
||||
|
||||
def test_bio_update_keeps_favorite_faction(client: TestClient):
|
||||
"""Частичный PATCH: правка «о себе» не должна обнулять выбранную фракцию."""
|
||||
login(client, "Аккуратный")
|
||||
fid = client.get("/api/factions").json()[0]["id"]
|
||||
client.patch(
|
||||
"/api/users/me/profile", json={"favorite_faction_id": fid}, headers=csrf_headers(client)
|
||||
)
|
||||
|
||||
r = client.patch("/api/users/me/profile", json={"bio": "Только био"}, headers=csrf_headers(client))
|
||||
assert r.status_code == 200
|
||||
assert r.json()["bio"] == "Только био"
|
||||
assert r.json()["favorite_faction_id"] == fid
|
||||
|
||||
|
||||
def test_prepositional_dictionary_and_fallback():
|
||||
"""Падежная форма берётся из словаря; незнакомый код — именительный как есть."""
|
||||
from app.services import faction_service
|
||||
|
||||
assert faction_service.prepositional("orks", "Орки") == "Орках"
|
||||
assert (
|
||||
faction_service.prepositional("astra_militarum", "Имперская гвардия")
|
||||
== "Имперской гвардии"
|
||||
)
|
||||
# Фракция, заведённая админом мимо словаря, не роняет вывод.
|
||||
assert faction_service.prepositional("custom_xeno", "Ксеносы") == "Ксеносы"
|
||||
|
||||
Reference in New Issue
Block a user