События: общий рейтинг обновляется у всех, а не только в группе партии
Рейтинг с #80 один на всё приложение: завершённая или удалённая партия двигает топ, историю и профили всех, кто играл после неё, и страницы других групп. Событие match по-прежнему идёт группе партии, остальным активным игрокам — ratings без подробностей о партии. Фронт по обоим сбрасывает все рейтинговые витрины (invalidateRatingViews); participant_ids больше не нужен. #88 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"""Событие партии несёт список участников: по нему клиент решает, чьи витрины протухли."""
|
||||
"""Адресаты событий партии. Рейтинг общий (#80): завершённая партия двигает витрины
|
||||
всех игроков, поэтому игроки вне группы получают событие ratings (#88)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -16,20 +17,29 @@ def _capture_events(monkeypatch) -> list[tuple[list[int], dict]]:
|
||||
return published
|
||||
|
||||
|
||||
def _match_events(published: list[tuple[list[int], dict]]) -> list[dict]:
|
||||
return [e for _ids, e in published if e.get("type") == "match"]
|
||||
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 test_match_event_carries_participants(client: TestClient, engine, monkeypatch):
|
||||
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()]
|
||||
gid = client.post(
|
||||
"/api/groups", json={"name": "Группа", "expansion_ids": exps}, headers=csrf_headers(client)
|
||||
).json()["id"]
|
||||
|
||||
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")
|
||||
# Третий в группе, но НЕ в партии: его история от этой партии не меняется.
|
||||
p3 = add_group_member(engine, gid, "Зритель")
|
||||
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(
|
||||
@@ -37,36 +47,31 @@ def test_match_event_carries_participants(client: TestClient, engine, monkeypatc
|
||||
[{"user_id": me["id"], "faction_id": fids[0]}, {"user_id": p2, "faction_id": fids[1]}],
|
||||
)
|
||||
assert started.status_code == 200, started.text
|
||||
mid = started.json()["id"]
|
||||
|
||||
ev = _match_events(published)[-1]
|
||||
assert sorted(ev["participant_ids"]) == sorted([me["id"], p2])
|
||||
assert p3 not in ev["participant_ids"]
|
||||
# Незавершённая партия рейтинг не двигает — знать о ней нужно только группе.
|
||||
assert _recipients(published, "match") == {me["id"], p2}
|
||||
assert _recipients(published, "ratings") == set()
|
||||
|
||||
published.clear()
|
||||
fin = finish_match(
|
||||
client, mid, [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}]
|
||||
client, started.json()["id"], [{"user_id": me["id"], "place": 1}, {"user_id": p2, "place": 2}]
|
||||
)
|
||||
assert fin.status_code == 200, fin.text
|
||||
assert sorted(_match_events(published)[-1]["participant_ids"]) == sorted([me["id"], p2])
|
||||
assert _recipients(published, "match") == {me["id"], p2}
|
||||
ratings = _recipients(published, "ratings")
|
||||
assert outsider in ratings
|
||||
assert not ratings & {me["id"], p2} # группа уже получила подробное событие
|
||||
|
||||
|
||||
def test_delete_event_carries_participants(client: TestClient, engine, monkeypatch):
|
||||
"""Удаление — главный случай: строки участников уже уничтожены каскадом.
|
||||
|
||||
Если собирать их после удаления, список всегда окажется пустым, и клиент не
|
||||
обновит историю тем, кто в этой партии играл."""
|
||||
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()]
|
||||
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"]
|
||||
@@ -74,4 +79,5 @@ def test_delete_event_carries_participants(client: TestClient, engine, monkeypat
|
||||
f"/api/matches/{mid}", params={"expected_version": version}, headers=csrf_headers(client)
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert sorted(_match_events(published)[-1]["participant_ids"]) == sorted([me["id"], p2])
|
||||
assert _recipients(published, "match") == {me["id"], p2}
|
||||
assert outsider in _recipients(published, "ratings")
|
||||
|
||||
Reference in New Issue
Block a user