"""Адресаты событий партии. Рейтинг общий (#80): завершённая партия двигает витрины всех игроков, поэтому игроки вне группы получают событие ratings (#88).""" from __future__ import annotations from fastapi.testclient import TestClient from tests.conftest import add_group_member, csrf_headers, finish_match, login, start_match def _capture_events(monkeypatch) -> list[tuple[list[int], dict]]: from app.core import events published: list[tuple[list[int], dict]] = [] monkeypatch.setattr( events.hub, "publish", lambda ids, event: published.append((list(ids), event)) ) return published def _recipients(published: list[tuple[list[int], dict]], kind: str) -> set[int]: return {uid for ids, e in published if e.get("type") == kind for uid in ids} def _two_groups(client: TestClient, engine) -> tuple[dict, int, int, int, list[int]]: """Хост и Игрок2 в группе партии, Чужой — только в другой группе хоста.""" me = login(client, "Хост") exps = [e["id"] for e in client.get("/api/expansions").json()] def group(name: str) -> int: return client.post( "/api/groups", json={"name": name, "expansion_ids": exps}, headers=csrf_headers(client) ).json()["id"] gid, other_gid = group("Группа"), group("Другая") p2 = add_group_member(engine, gid, "Игрок2") outsider = add_group_member(engine, other_gid, "Чужой") fids = [f["id"] for f in client.get(f"/api/groups/{gid}/factions").json()] return me, gid, p2, outsider, fids def test_finished_match_reaches_players_outside_group(client: TestClient, engine, monkeypatch): me, gid, p2, outsider, fids = _two_groups(client, engine) published = _capture_events(monkeypatch) 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 # Незавершённая партия рейтинг не двигает — знать о ней нужно только группе. assert _recipients(published, "match") == {me["id"], p2} assert _recipients(published, "ratings") == set() published.clear() fin = finish_match( client, started.json()["id"], [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}] ) assert fin.status_code == 200, fin.text assert _recipients(published, "match") == {me["id"], p2} ratings = _recipients(published, "ratings") assert outsider in ratings assert not ratings & {me["id"], p2} # группа уже получила подробное событие def test_deleting_finished_match_reaches_players_outside_group( client: TestClient, engine, monkeypatch ): """Удаление завершённой партии пересчитывает рейтинг всех, кто играл после неё.""" me, gid, p2, outsider, fids = _two_groups(client, engine) mid = start_match( client, gid, [{"user_id": me["id"], "faction_id": fids[0]}, {"user_id": p2, "faction_id": fids[1]}], ).json()["id"] finish_match(client, mid, [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}]) published = _capture_events(monkeypatch) version = client.get(f"/api/matches/{mid}").json()["version"] r = client.delete( f"/api/matches/{mid}", params={"expected_version": version}, headers=csrf_headers(client) ) assert r.status_code == 200, r.text assert _recipients(published, "match") == {me["id"], p2} assert outsider in _recipients(published, "ratings") def _finished_match(client: TestClient, me: dict, p2: int, gid: int, fids: list[int]) -> int: mid = start_match( client, gid, [{"user_id": me["id"], "faction_id": fids[0]}, {"user_id": p2, "faction_id": fids[1]}], ).json()["id"] finish_match(client, mid, [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}]) return mid def _patch_match(client: TestClient, mid: int, me: dict, p2: int, fids: list[int], **changes): """PATCH как у формы правки: состав передаётся целиком, даже если меняется комментарий.""" places = changes.pop("places", (1, 2)) body = { "expected_version": client.get(f"/api/matches/{mid}").json()["version"], "participants": [ {"user_id": me["id"], "faction_id": fids[0], "place": places[0]}, {"user_id": p2, "faction_id": fids[1], "place": places[1]}, ], **changes, } r = client.patch(f"/api/matches/{mid}", json=body, headers=csrf_headers(client)) assert r.status_code == 200, r.text def test_comment_edit_does_not_broadcast_ratings(client: TestClient, engine, monkeypatch): """Правка без влияния на рейтинг — событие только группе: остальным игрокам незачем перезапрашивать все рейтинговые витрины.""" me, gid, p2, outsider, fids = _two_groups(client, engine) mid = _finished_match(client, me, p2, gid, fids) published = _capture_events(monkeypatch) _patch_match(client, mid, me, p2, fids, overall_comment="Хорошая партия") assert _recipients(published, "match") == {me["id"], p2} assert _recipients(published, "ratings") == set() def test_result_edit_broadcasts_ratings(client: TestClient, engine, monkeypatch): me, gid, p2, outsider, fids = _two_groups(client, engine) mid = _finished_match(client, me, p2, gid, fids) published = _capture_events(monkeypatch) _patch_match(client, mid, me, p2, fids, places=(2, 1)) assert outsider in _recipients(published, "ratings") def test_group_settings_change_reaches_members(client: TestClient, engine, monkeypatch): """Переключение домашнего правила видно остальным участникам без перезагрузки.""" me, gid, p2, outsider, fids = _two_groups(client, engine) published = _capture_events(monkeypatch) r = client.patch( f"/api/groups/{gid}", json={"nine_rounds_rule": True}, headers=csrf_headers(client) ) assert r.status_code == 200, r.text assert _recipients(published, "group") == {me["id"], p2}