Правка завершённой партии проверяла состав теми же правилами, что и создание: участник обязан состоять в группе сейчас, фракция — быть доступной сейчас. После отключения дополнения партию, сыгранную на Тау, было уже не исправить, а после удаления игрока из группы — любую партию с ним. Теперь то, что уже записано в партии, проходит всегда, а новые игроки и фракции по-прежнему берутся только из текущего состава: чинить историю можно, занести в неё постороннего — нет. Создание партии не ослабло. Править завершённую партию умел любой участник группы, но только через API — кнопки не было, и на практике это мог сделать лишь админ через админку. Теперь у блока «Результаты» есть «Редактировать», и правка идёт тем же перетаскиванием, что и завершение: раскладка восстанавливается из сохранённых мест (одинаковое место — ничья, выбывшие отдельно), рядом — фракции, причина победы и комментарии. Сохранение шлёт версию партии, так что устаревшая правка отклоняется как раньше. Фракции вынесены отдельным блоком, а не в PlaceEditor: он занят перетаскиванием, и селекты внутри него — лишний риск. В списке доступных фракций к набору группы добавляются те, что уже стоят в партии, — иначе фракцию из отключённого дополнения нельзя было бы даже оставить как есть. #29 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
143 lines
6.0 KiB
Python
143 lines
6.0 KiB
Python
"""Правка завершённой партии игроком: история чинится после изменений в группе."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from tests.conftest import add_group_member, create_finished_match, csrf_headers, login
|
|
|
|
|
|
def _exp_id(client: TestClient, code: str) -> int:
|
|
return next(e["id"] for e in client.get("/api/expansions").json() if e["code"] == code)
|
|
|
|
|
|
def _played(client: TestClient, engine) -> tuple[dict, int, int, int, dict]:
|
|
"""Партия «Аня против Бори» в группе с обоими дополнениями."""
|
|
me = login(client, "Аня")
|
|
fw, fv = _exp_id(client, "forgotten_worlds"), _exp_id(client, "forsaken_voids")
|
|
gid = client.post(
|
|
"/api/groups", json={"name": "Группа", "expansion_ids": [fw, fv]},
|
|
headers=csrf_headers(client),
|
|
).json()["id"]
|
|
b = add_group_member(engine, gid, "Боря")
|
|
factions = {f["code"]: f["id"] for f in client.get(f"/api/groups/{gid}/factions").json()}
|
|
mid = create_finished_match(
|
|
client, gid,
|
|
[
|
|
{"user_id": me["id"], "faction_id": factions["orks"], "place": 1},
|
|
{"user_id": b, "faction_id": factions["tau"], "place": 2},
|
|
],
|
|
)["id"]
|
|
return me, gid, b, mid, factions
|
|
|
|
|
|
def _swap_places(client: TestClient, mid: int, me_id: int, b: int, factions: dict) -> dict:
|
|
detail = client.get(f"/api/matches/{mid}").json()
|
|
fid = {p["user_id"]: p["faction_id"] for p in detail["participants"]}
|
|
return {
|
|
"participants": [
|
|
{"user_id": me_id, "faction_id": fid[me_id], "place": 2},
|
|
{"user_id": b, "faction_id": fid[b], "place": 1},
|
|
],
|
|
"expected_version": detail["version"],
|
|
}
|
|
|
|
|
|
def test_edit_after_expansion_disabled(client: TestClient, engine):
|
|
"""Дополнение выключили — партия на Тау всё равно правится.
|
|
|
|
Регрессия: правка проверяла фракции по ТЕКУЩЕМУ набору группы, и партия,
|
|
сыгранная на фракции из отключённого дополнения, становилась неисправимой."""
|
|
me, gid, b, mid, factions = _played(client, engine)
|
|
|
|
off = client.put(
|
|
f"/api/groups/{gid}/expansions",
|
|
json={"expansion_ids": [_exp_id(client, "forsaken_voids")]},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert off.status_code == 200, off.text
|
|
|
|
r = client.patch(
|
|
f"/api/matches/{mid}", json=_swap_places(client, mid, me["id"], b, factions),
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
places = {p["user_id"]: p["place"] for p in client.get(f"/api/matches/{mid}").json()["participants"]}
|
|
assert places[b] == 1 and places[me["id"]] == 2
|
|
|
|
|
|
def test_edit_after_player_left_group(client: TestClient, engine):
|
|
"""Игрока убрали из группы — партия с ним всё равно правится."""
|
|
me, gid, b, mid, factions = _played(client, engine)
|
|
|
|
out = client.delete(f"/api/groups/{gid}/members/{b}", headers=csrf_headers(client))
|
|
assert out.status_code == 200, out.text
|
|
|
|
r = client.patch(
|
|
f"/api/matches/{mid}", json=_swap_places(client, mid, me["id"], b, factions),
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
|
|
|
|
def test_edit_rejects_new_outsider_and_unavailable_faction(client: TestClient, engine):
|
|
"""Послабление — только для того, что уже в партии.
|
|
|
|
Вписать нового игрока не из группы или фракцию, которой в партии не было и у
|
|
группы нет, по-прежнему нельзя: иначе в историю можно занести что угодно."""
|
|
me, gid, b, mid, factions = _played(client, engine)
|
|
stranger = client.post(
|
|
"/api/auth/dev/users", json={"nickname": "Чужак"}, headers=csrf_headers(client)
|
|
).json()
|
|
detail = client.get(f"/api/matches/{mid}").json()
|
|
fid = {p["user_id"]: p["faction_id"] for p in detail["participants"]}
|
|
|
|
bad_user = client.patch(
|
|
f"/api/matches/{mid}",
|
|
json={
|
|
"participants": [
|
|
{"user_id": me["id"], "faction_id": fid[me["id"]], "place": 1},
|
|
{"user_id": stranger["id"], "faction_id": fid[b], "place": 2},
|
|
]
|
|
},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert bad_user.status_code == 422, bad_user.text
|
|
|
|
# Выключаем дополнение и пробуем поставить ЕГО фракцию, которой в партии не было.
|
|
assert client.put(
|
|
f"/api/groups/{gid}/expansions",
|
|
json={"expansion_ids": [_exp_id(client, "forsaken_voids")]},
|
|
headers=csrf_headers(client),
|
|
).status_code == 200
|
|
bad_faction = client.patch(
|
|
f"/api/matches/{mid}",
|
|
json={
|
|
"participants": [
|
|
{"user_id": me["id"], "faction_id": factions["necrons"], "place": 1},
|
|
{"user_id": b, "faction_id": fid[b], "place": 2},
|
|
]
|
|
},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert bad_faction.status_code == 422, bad_faction.text
|
|
|
|
|
|
def test_create_match_still_validated(client: TestClient, engine):
|
|
"""Создание партии не ослабло: посторонний игрок по-прежнему отклоняется."""
|
|
me, gid, b, mid, factions = _played(client, engine)
|
|
stranger = client.post(
|
|
"/api/auth/dev/users", json={"nickname": "Чужак2"}, headers=csrf_headers(client)
|
|
).json()
|
|
r = client.post(
|
|
"/api/matches",
|
|
json={
|
|
"group_id": gid,
|
|
"participants": [
|
|
{"user_id": me["id"], "faction_id": factions["orks"]},
|
|
{"user_id": stranger["id"], "faction_id": factions["eldar"]},
|
|
],
|
|
},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert r.status_code == 422, r.text
|