"""Медиа партии (фото): загрузка/просмотр/удаление, валидация, лимит, запрет после финиша.""" from __future__ import annotations from fastapi.testclient import TestClient from tests.conftest import add_group_member, csrf_headers, finish_match, login, start_match 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 _start(client: TestClient, engine) -> tuple[dict, int, 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, gid, p2, started.json()["id"] def _upload(client: TestClient, mid: int, name: str = "a.png", data: bytes = PNG, mime: str = "image/png"): return client.post( f"/api/matches/{mid}/attachments", files={"file": (name, data, mime)}, headers=csrf_headers(client), ) def test_upload_view_delete(client: TestClient, engine, monkeypatch, tmp_path): _use_tmp_uploads(monkeypatch, tmp_path) me, gid, p2, mid = _start(client, engine) r = _upload(client, mid) assert r.status_code == 200, r.text aid = r.json()["id"] assert r.json()["url"] == f"/api/matches/{mid}/attachments/{aid}" detail = client.get(f"/api/matches/{mid}").json() assert [a["id"] for a in detail["attachments"]] == [aid] g = client.get(f"/api/matches/{mid}/attachments/{aid}") assert g.status_code == 200 and g.content == PNG assert g.headers["content-type"] == "image/png" d = client.delete(f"/api/matches/{mid}/attachments/{aid}", headers=csrf_headers(client)) assert d.status_code == 200, d.text assert client.get(f"/api/matches/{mid}").json()["attachments"] == [] def test_reject_non_image_and_limit(client: TestClient, engine, monkeypatch, tmp_path): _use_tmp_uploads(monkeypatch, tmp_path) _me, _gid, _p2, mid = _start(client, engine) assert _upload(client, mid, "x.txt", b"nope", "text/plain").status_code == 422 for i in range(10): assert _upload(client, mid, f"{i}.png").status_code == 200 assert _upload(client, mid, "over.png").status_code == 409 def test_no_changes_after_finish(client: TestClient, engine, monkeypatch, tmp_path): _use_tmp_uploads(monkeypatch, tmp_path) me, gid, p2, mid = _start(client, engine) aid = _upload(client, mid).json()["id"] 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 # После завершения игрок не может ни добавлять, ни удалять. assert _upload(client, mid).status_code == 409 assert client.delete( f"/api/matches/{mid}/attachments/{aid}", headers=csrf_headers(client) ).status_code == 409 # Но просмотр сохраняется. assert client.get(f"/api/matches/{mid}").json()["attachments"][0]["id"] == aid def test_admin_manage_attachments_on_finished( client: TestClient, engine, make_admin, monkeypatch, tmp_path ): _use_tmp_uploads(monkeypatch, tmp_path) me, gid, p2, mid = _start(client, engine) 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 make_admin("admin", "secret123") assert client.post( "/api/admin/auth/login", json={"username": "admin", "password": "secret123"}, headers=csrf_headers(client), ).status_code == 200 # Админ добавляет медиа к ЗАВЕРШЁННОЙ партии (игроку это запрещено). up = client.post( f"/api/admin/matches/{mid}/attachments", files={"file": ("a.png", PNG, "image/png")}, headers=csrf_headers(client), ) assert up.status_code == 200, up.text aid = up.json()["id"] assert up.json()["url"] == f"/api/admin/matches/{mid}/attachments/{aid}" assert any(a["id"] == aid for a in client.get(f"/api/admin/matches/{mid}/attachments").json()) g = client.get(f"/api/admin/matches/{mid}/attachments/{aid}") assert g.status_code == 200 and g.content == PNG d = client.delete( f"/api/admin/matches/{mid}/attachments/{aid}", headers=csrf_headers(client) ) assert d.status_code == 200, d.text assert client.get(f"/api/admin/matches/{mid}/attachments").json() == [] def test_non_member_cannot_view(client: TestClient, engine, monkeypatch, tmp_path): _use_tmp_uploads(monkeypatch, tmp_path) _me, _gid, _p2, mid = _start(client, engine) aid = _upload(client, mid).json()["id"] login(client, "Чужак") # не состоит в группе g = client.get(f"/api/matches/{mid}/attachments/{aid}") assert g.status_code in (401, 403)