"""Оптимистичная блокировка партии: устаревшие правки/удаление отклоняются (STALE_WRITE).""" from __future__ import annotations from fastapi.testclient import TestClient from tests.conftest import add_group_member, csrf_headers, finish_match, login, start_match def _start(client: TestClient, engine) -> tuple[dict, int, int]: me = login(client, "Хост") 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, "Игрок2") fids = [f["id"] for f in client.get(f"/api/groups/{gid}/factions").json()] started = start_match( client, gid, [{"user_id": me["id"], "faction_id": fids[0]}, {"user_id": p2, "faction_id": fids[1]}], ) assert started.status_code == 200, started.text return me, p2, started.json()["id"] def test_stale_delete_rejected(client: TestClient, engine): """Сценарий бага: ПК завершил, телефон со старой версией жмёт «Отменить».""" me, p2, mid = _start(client, engine) v1 = client.get(f"/api/matches/{mid}").json()["version"] # «ПК» завершает партию — версия меняется. fin = finish_match( client, mid, [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}], win_reason="objectives", ) assert fin.status_code == 200, fin.text # «Телефон» со старой версией пытается отменить → 409 STALE_WRITE, партия НЕ удаляется. stale = client.delete( f"/api/matches/{mid}", params={"expected_version": v1}, headers=csrf_headers(client) ) assert stale.status_code == 409, stale.text assert stale.json()["error"]["code"] == "STALE_WRITE" assert client.get(f"/api/matches/{mid}").status_code == 200 # жива # С актуальной версией удаление проходит. v2 = client.get(f"/api/matches/{mid}").json()["version"] ok = client.delete( f"/api/matches/{mid}", params={"expected_version": v2}, headers=csrf_headers(client) ) assert ok.status_code == 200, ok.text assert client.get(f"/api/matches/{mid}").status_code == 404 def test_stale_finish_rejected(client: TestClient, engine): me, p2, mid = _start(client, engine) v1 = client.get(f"/api/matches/{mid}").json()["version"] # Партию изменили (правка комментария) — версия устарела. bump = client.patch( f"/api/matches/{mid}", json={"overall_comment": "правка"}, headers=csrf_headers(client) ) assert bump.status_code == 200, bump.text # Завершение со старой версией → 409 STALE_WRITE. r = client.post( f"/api/matches/{mid}/finish", json={ "participants": [ {"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}, ], "win_reason": "objectives", "expected_version": v1, }, headers=csrf_headers(client), ) assert r.status_code == 409 and r.json()["error"]["code"] == "STALE_WRITE", r.text