teecup/tests/test_rounds_stats_tournament.py
Erol Haagenrud c5b5f441da Bland org-turnering-runder inn i personlig statistikk-dashboard (ADR-105)
Punkt 3 i scoring-paritet-gap-listen: GET /rounds/stats/summary leste
tidligere kun frittstående runder. Ny _fetch_tournament_stats_rows()
speiler hole_history.py sin N+1-per-org-henting og blander resultatet
inn i samme by_round/stat_level_by_round/played_at_by_round-dict-er som
personlige rader -- ingen endring i selve aggregeringslogikken
(_resolve_stats_window/_build_round_series/_summarize_rounds er allerede
generiske). Kilde-antall vises separat i UI (samme presedens som
hole_history.py sin times_personal/times_tournament).

5 nye pytest-tester + full regresjon (193/193), tsc rent, full-stack
scratch-verifisert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 17:15:05 +02:00

172 lines
7.5 KiB
Python

"""
GET /rounds/stats/summary skal blande org-turnering-runder inn i de samme
trend-/snitt-tallene som frittstående runder (scoring-paritet-gap punkt 3,
se FEATURE_BACKLOG.md), med kilde-antall vist separat
(`rounds_completed_personal`/`rounds_completed_tournament`) -- speiler
`app/hole_history.py` sin `times_personal`/`times_tournament`-presedens.
Speiler `test_hole_history.py` sitt integrasjonsmønster (ekte router-
funksjoner, ikke rå SQL for selve scoringen) for personal/tournament-
scoring, men mot `get_rounds_stats_summary` i stedet for hull-historikk.
"""
from app.auth import CurrentUser
from app.routers.individual_tournaments import (
HoleUpdate as TournamentHoleUpdate,
update_hole as tournament_update_hole,
)
from app.routers.rounds import HoleUpdate as RoundHoleUpdate, get_rounds_stats_summary, update_hole as round_update_hole
from tests.conftest import (
add_membership,
create_hole,
create_org,
create_org_hole,
create_course,
create_participant,
create_player,
create_round,
create_tee,
create_tournament,
create_tournament_participant,
create_tournament_round,
create_tournament_round_participant,
create_user,
)
import app.db as app_db
async def _score_personal_hole(round_id, participant_id, user_id, hole_number, score, putts=None):
user = CurrentUser(user_id=user_id)
await round_update_hole(
round_id, participant_id, hole_number,
RoundHoleUpdate(score=score, putts=putts, expected_version=None),
user=user,
)
async def _score_tournament_hole(tournament_id, round_id, rp_id, org_id, user_id, hole_number, score, putts=None):
user = CurrentUser(user_id=user_id)
await tournament_update_hole(
tournament_id, round_id, rp_id, hole_number,
TournamentHoleUpdate(gross_strokes=score, putts=putts, expected_version=None),
organization_id=org_id, user=user,
)
async def _make_completed_personal_round(user_id, *, score_per_hole, putts_per_hole=None, par=4,
stat_level="strokes_and_putts"):
async with app_db.plain_connection() as conn:
round_id = await create_round(conn, user_id)
participant_id = await create_participant(conn, round_id, user_id=user_id, is_owner=True)
for n in range(1, 19):
await create_hole(conn, participant_id, hole_number=n, par=par, stroke_index=n)
await conn.execute("UPDATE round_participant SET stat_level = $1 WHERE id = $2", stat_level, participant_id)
for n in range(1, 19):
await _score_personal_hole(
round_id, participant_id, user_id, n, score_per_hole,
putts=putts_per_hole,
)
async with app_db.plain_connection() as conn:
await conn.execute("UPDATE round SET completed_at = now() WHERE id = $1", round_id)
return round_id, participant_id
async def _make_tournament_round(
user_id, *, score_per_hole, putts_per_hole=None, par=4, holes_scored=18,
stat_level="strokes_and_putts", set_scheduled_at=True, org_id=None,
):
if org_id is None:
org_id = await create_org()
await add_membership(org_id, user_id, role="member")
tournament_id = await create_tournament(org_id)
course_id = await create_course(org_id)
for n in range(1, 19):
await create_org_hole(org_id, course_id, hole_number=n, par=par, stroke_index=n)
tee_id = await create_tee(org_id, course_id)
tournament_round_id = await create_tournament_round(org_id, tournament_id, course_id)
if set_scheduled_at:
async with app_db.org_connection(org_id) as conn:
await conn.execute("UPDATE tournament_round SET scheduled_at = now() WHERE id = $1", tournament_round_id)
player_id = await create_player(org_id, user_id=user_id)
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
async with app_db.org_connection(org_id) as conn:
await conn.execute("UPDATE tournament_participant SET stat_level = $1 WHERE id = $2", stat_level, tp_id)
rp_id = await create_tournament_round_participant(org_id, tournament_round_id, tp_id, tee_id)
for n in range(1, holes_scored + 1):
await _score_tournament_hole(
tournament_id, tournament_round_id, rp_id, org_id, user_id, n, score_per_hole,
putts=putts_per_hole,
)
return org_id, tournament_round_id, rp_id
async def test_blends_personal_and_tournament_rounds(pool):
user_id = await create_user()
await _make_completed_personal_round(user_id, score_per_hole=5, putts_per_hole=2, par=4) # +18 til par
await _make_tournament_round(user_id, score_per_hole=4, putts_per_hole=2, par=4) # +0 til par
out = await get_rounds_stats_summary(window="all", user=CurrentUser(user_id=user_id))
assert out.current.rounds_completed == 2
assert out.current.rounds_completed_personal == 1
assert out.current.rounds_completed_tournament == 1
assert out.current.avg_score_to_par == 9.0 # (18 + 0) / 2
async def test_tournament_strokes_only_round_excluded_from_putts(pool):
user_id = await create_user()
await _make_completed_personal_round(user_id, score_per_hole=4, putts_per_hole=2, par=4)
await _make_tournament_round(
user_id, score_per_hole=4, putts_per_hole=None, par=4, stat_level="strokes_only",
)
out = await get_rounds_stats_summary(window="all", user=CurrentUser(user_id=user_id))
assert out.current.rounds_completed == 2
assert out.current.rounds_completed_tournament == 1
# Kun den frittstående runden sporet putter -- strokes_only-turnerings-
# runden skal IKKE forurense putt-snittet (samme regel som gjelder for
# en frittstående strokes_only-runde).
assert out.current.putts_tracked_rounds == 1
assert out.current.avg_putts_per_18 == 36.0
async def test_incomplete_tournament_round_is_excluded(pool):
user_id = await create_user()
await _make_completed_personal_round(user_id, score_per_hole=4, putts_per_hole=2, par=4)
await _make_tournament_round(user_id, score_per_hole=4, holes_scored=10) # kun 10 av 18 hull
out = await get_rounds_stats_summary(window="all", user=CurrentUser(user_id=user_id))
assert out.current.rounds_completed == 1
assert out.current.rounds_completed_personal == 1
assert out.current.rounds_completed_tournament == 0
async def test_tournament_round_without_scheduled_at_is_excluded(pool):
user_id = await create_user()
await _make_completed_personal_round(user_id, score_per_hole=4, putts_per_hole=2, par=4)
await _make_tournament_round(user_id, score_per_hole=4, set_scheduled_at=False)
out = await get_rounds_stats_summary(window="all", user=CurrentUser(user_id=user_id))
assert out.current.rounds_completed == 1
assert out.current.rounds_completed_tournament == 0
async def test_tournament_round_in_foreign_org_without_player_row_is_excluded(pool):
"""`player_organizations_for_user` returnerer kun org-er brukeren har en
`player`-rad i -- en fullført turneringsrunde i en org der brukeren
IKKE er spiller skal aldri kunne telles med i brukerens egen statistikk."""
user_id = await create_user()
other_user_id = await create_user()
await _make_completed_personal_round(user_id, score_per_hole=4, putts_per_hole=2, par=4)
# Turneringsrunde scoret for en ANNEN bruker, i en org `user_id` ikke er medlem/spiller i.
await _make_tournament_round(other_user_id, score_per_hole=4, putts_per_hole=2, par=4)
out = await get_rounds_stats_summary(window="all", user=CurrentUser(user_id=user_id))
assert out.current.rounds_completed == 1
assert out.current.rounds_completed_tournament == 0