"""Тесты: лимиты (группа/партия), правка партии админом, переименование фракции, фильтры.""" from __future__ import annotations from fastapi.testclient import TestClient from tests.conftest import add_group_member, create_finished_match, csrf_headers, login, start_match 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 _admin_login(client: TestClient, make_admin) -> None: make_admin("admin", "secret123") r = client.post( "/api/admin/auth/login", json={"username": "admin", "password": "secret123"}, headers=csrf_headers(client), ) assert r.status_code == 200, r.text def test_group_invite_capacity_limit(client: TestClient): login(client, "Owner") gid = client.post( "/api/groups", json={"name": "Большая", "expansion_ids": []}, headers=csrf_headers(client) ).json()["id"] for i in range(1, 11): client.post("/api/auth/dev/users", json={"nickname": f"U{i}"}, headers=csrf_headers(client)) statuses = [] for i in range(1, 11): r = client.post( f"/api/groups/{gid}/invitations", json={"nickname": f"U{i}"}, headers=csrf_headers(client) ) statuses.append(r.status_code) # owner(1) + 9 приглашений = 10 (лимит мест); 10-е приглашение сверх лимита → 409. assert statuses[:9] == [200] * 9 assert statuses[9] == 409 def test_match_player_limit(client: TestClient, engine): me = login(client, "Owner") 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"] ids = [me["id"]] for i in range(1, 7): ids.append(add_group_member(engine, gid, f"P{i}")) factions = client.get(f"/api/groups/{gid}/factions").json() fids = [f["id"] for f in factions] # 7 игроков на старте → 422 (максимум 6). roster = [{"user_id": uid, "faction_id": fids[i]} for i, uid in enumerate(ids)] r = start_match(client, gid, roster) assert r.status_code == 422 assert "6" in r.json()["error"]["message"] def test_admin_edit_match_and_filters(client: TestClient, make_admin, engine): me = login(client, "Аня") gid = client.post( "/api/groups", json={"name": "Группа", "expansion_ids": []}, 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["eldar"], "place": 2}, ], )["id"] _admin_login(client, make_admin) # Фильтры: по игроку и по фракции находят партию. assert any(m["id"] == mid for m in client.get(f"/api/admin/matches?user_id={b}").json()) assert any( m["id"] == mid for m in client.get(f"/api/admin/matches?faction_id={factions['orks']}").json() ) # Правка: меняем фракцию Бори eldar → chaos. r = client.patch( f"/api/admin/matches/{mid}", json={ "participants": [ {"user_id": me["id"], "faction_id": factions["orks"], "place": 1}, {"user_id": b, "faction_id": factions["chaos"], "place": 2}, ] }, headers=csrf_headers(client), ) assert r.status_code == 200, r.text detail = client.get(f"/api/admin/matches/{mid}").json() bp = next(p for p in detail["participants"] if p["user_id"] == b) assert bp["faction_name"] == "Хаоситы" def test_admin_rename_account(client: TestClient, make_admin): """Админ переименовывает аккаунт: валидируется формат и уникальность ника.""" a = login(client, "Старое") client.post("/api/auth/dev/users", json={"nickname": "Занятое"}, headers=csrf_headers(client)) _admin_login(client, make_admin) r = client.patch( f"/api/admin/users/{a['id']}", json={"nickname": "Новое"}, headers=csrf_headers(client) ) assert r.status_code == 200, r.text assert r.json()["nickname"] == "Новое" # Занятый ник → 409, некорректный формат → 422. r = client.patch( f"/api/admin/users/{a['id']}", json={"nickname": "Занятое"}, headers=csrf_headers(client) ) assert r.status_code == 409 and r.json()["error"]["code"] == "NICKNAME_TAKEN", r.text r = client.patch( f"/api/admin/users/{a['id']}", json={"nickname": "x"}, headers=csrf_headers(client) ) assert r.status_code == 422, r.text def test_dev_delete_account_keeps_matches(client: TestClient, make_admin, engine): """DEV-удаление: аккаунт удаляется, партии живут (игрок вычёркивается), его созданные партии и группы переназначаются на админа.""" me = login(client, "Аня") # владелец группы и создатель партии gid = client.post( "/api/groups", json={"name": "Группа", "expansion_ids": []}, 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["eldar"], "place": 2}, ], )["id"] admin_id = make_admin("admin", "secret123") r = client.post( "/api/admin/auth/login", json={"username": "admin", "password": "secret123"}, headers=csrf_headers(client), ) assert r.status_code == 200, r.text # Удаляем участника Борю → партия жива, его строка участия вычеркнута. r = client.delete(f"/api/admin/dev/users/{b}", headers=csrf_headers(client)) assert r.status_code == 200, r.text assert all(u["id"] != b for u in client.get("/api/admin/users").json()) detail = client.get(f"/api/admin/matches/{mid}").json() assert [p["user_id"] for p in detail["participants"]] == [me["id"]] # Боря вычеркнут assert detail["player_count"] == 2 # player_count НЕ пересчитывается (осознанно) # Удаляем создателя/владельца Аню → партия и группа сохраняются, переходят к админу. r = client.delete(f"/api/admin/dev/users/{me['id']}", headers=csrf_headers(client)) assert r.status_code == 200, r.text assert client.get(f"/api/admin/matches/{mid}").json()["created_by"] == admin_id grp = next(g for g in client.get("/api/admin/groups").json() if g["id"] == gid) assert grp["owner_id"] == admin_id def test_dev_delete_account_rejects_admin(client: TestClient, make_admin): """Админа удалить нельзя.""" admin_id = make_admin("admin", "secret123") r = client.post( "/api/admin/auth/login", json={"username": "admin", "password": "secret123"}, headers=csrf_headers(client), ) assert r.status_code == 200, r.text r = client.delete(f"/api/admin/dev/users/{admin_id}", headers=csrf_headers(client)) assert r.status_code == 422, r.text def test_admin_rename_faction_system_wide(client: TestClient, make_admin, engine): me = login(client, "Кто-то") gid = client.post( "/api/groups", json={"name": "Группа", "expansion_ids": []}, 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["eldar"], "place": 2}, ], )["id"] _admin_login(client, make_admin) r = client.patch( f"/api/admin/factions/{factions['orks']}", json={"name_ru": "Орки WAAAGH"}, headers=csrf_headers(client), ) assert r.status_code == 200, r.text # Изменение отражается в существующей партии (имя хранится один раз). detail = client.get(f"/api/admin/matches/{mid}").json() ap = next(p for p in detail["participants"] if p["user_id"] == me["id"]) assert ap["faction_name"] == "Орки WAAAGH" def test_admin_delete_group_with_matches_is_conflict(client: TestClient, make_admin, engine): """Группу с партиями удалять нельзя — но ответ должен быть внятным 409. Регрессия: matches.group_id — ON DELETE RESTRICT, и голый session.delete ронял IntegrityError наружу пятисоткой без конверта ошибки.""" me = login(client, "Owner") gid = client.post( "/api/groups", json={"name": "Группа", "expansion_ids": []}, 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()] create_finished_match( client, gid, [ {"user_id": me["id"], "faction_id": fids[0], "place": 1}, {"user_id": p2, "faction_id": fids[1], "place": 2}, ], ) _admin_login(client, make_admin) r = client.delete(f"/api/admin/groups/{gid}", headers=csrf_headers(client)) assert r.status_code == 409, r.text assert client.get("/api/admin/groups").status_code == 200 def test_last_owner_cannot_demote_self(client: TestClient, engine): """Единственный владелец не может разжаловать сам себя. Регрессия: change_role не проверял последнего владельца (в отличие от удаления), и группа оставалась без владельца навсегда — назначить нового было некому.""" me = login(client, "Owner") gid = client.post( "/api/groups", json={"name": "Группа", "expansion_ids": []}, headers=csrf_headers(client) ).json()["id"] add_group_member(engine, gid, "Игрок2") r = client.patch( f"/api/groups/{gid}/members/{me['id']}", json={"role": "member"}, headers=csrf_headers(client), ) assert r.status_code == 403, r.text members = client.get(f"/api/groups/{gid}/members").json() assert any(m["user_id"] == me["id"] and m["role"] == "owner" for m in members)