Рейтинг с #80 один на всё приложение: завершённая или удалённая партия двигает топ, историю и профили всех, кто играл после неё, и страницы других групп. Событие match по-прежнему идёт группе партии, остальным активным игрокам — ratings без подробностей о партии. Фронт по обоим сбрасывает все рейтинговые витрины (invalidateRatingViews); participant_ids больше не нужен. #88 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
84 lines
3.7 KiB
Python
84 lines
3.7 KiB
Python
"""Адресаты событий партии. Рейтинг общий (#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")
|