teecup/tests/test_participant_status.py
Erol Haagenrud 396fe40b7c Manuell deltakerstatus (DSQ/RTD/DNF/DNS) + spillerpool-kobling (ADR-091)
Ny status-kolonne på tournament_participant, overstyrer leaderboardets
total og blokkerer score i alle runder. Ny direkte lenke fra
turneringens Deltakere-liste til riktig rad i org-spillerpoolen.
Migrasjon 087 lagt til, men IKKE anvendt mot ekte teecup_db ennå.
2026-08-18 18:06:49 +02:00

170 lines
7.2 KiB
Python

"""
Manuell status per deltaker (migrasjon 087, 2026-08-18). Bruker: "Vi må
manuelt kunne sette Dsq, Rtd, Dnf, og fns [DNS]." Tournament-bredt
(ikke per runde), overstyrer BÅDE det beregnede til-par-tallet og en
ev. Cut-status, og blokkerer videre score-registrering i ALLE runder
(strengere enn Cut, som kun blokkerer runder ETTER cut-punktet).
"""
import pytest
from fastapi import HTTPException
from app.auth import CurrentUser
from app.routers.individual_tournaments import (
HoleUpdate as TournamentHoleUpdate,
TournamentParticipantUpdate,
individual_leaderboard,
update_hole as tournament_update_hole,
update_tournament_participant,
)
from tests.conftest import (
add_membership,
create_org,
create_org_hole,
create_course,
create_player,
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_holes(tournament_id, round_id, rp_id, org_id, user_id, scores: list[int]) -> None:
user = CurrentUser(user_id=user_id)
for n, gross in enumerate(scores, start=1):
await tournament_update_hole(
tournament_id, round_id, rp_id, n,
TournamentHoleUpdate(gross_strokes=gross, expected_version=None),
organization_id=org_id, user=user,
)
async def _setup(org_id, owner_id, method="stroke_gross"):
tournament_id = await create_tournament(org_id, name="Status-turnering")
course_id = await create_course(org_id, name="Status Links")
for n in range(1, 19):
await create_org_hole(org_id, course_id, hole_number=n, par=4, stroke_index=n)
tee_id = await create_tee(org_id, course_id)
async with app_db.org_connection(org_id) as conn:
await conn.execute(
"UPDATE tournament SET format_type = 'individual', scoring_method = $2 WHERE id = $1",
tournament_id, method,
)
round1_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=1)
round2_id = await create_tournament_round(org_id, tournament_id, course_id, sequence=2)
return tournament_id, course_id, tee_id, round1_id, round2_id
@pytest.mark.parametrize("status", ["dsq", "rtd", "dnf", "dns"])
async def test_status_overrides_computed_total_label(pool, status):
org_id = await create_org()
owner_id = await create_user()
await add_membership(org_id, owner_id, role="owner")
tournament_id, course_id, tee_id, round1_id, round2_id = await _setup(org_id, owner_id)
player_id = await create_player(org_id, display_name="Spiller")
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
rp_id = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
await _score_holes(tournament_id, round1_id, rp_id, org_id, owner_id, [4] * 18)
await update_tournament_participant(
tournament_id, tp_id, TournamentParticipantUpdate(status=status), organization_id=org_id
)
entries = await individual_leaderboard(tournament_id, organization_id=org_id)
entry = next(e for e in entries if e.tournament_participant_id == tp_id)
assert entry.total_label == status.upper()
assert entry.is_leader is False
assert entry.position is None
assert entry.status == status
async def test_status_overrides_even_a_cut_survivor(pool):
"""En DSQ-et spiller skal vises som DSQ, ikke CUT, selv om de
tilfeldigvis også overlevde cutten."""
org_id = await create_org()
owner_id = await create_user()
await add_membership(org_id, owner_id, role="owner")
tournament_id, course_id, tee_id, round1_id, round2_id = await _setup(org_id, owner_id)
from app.routers.tournaments import TournamentUpdate, update_tournament
from app.routers.individual_tournaments import apply_cut
await update_tournament(
tournament_id, TournamentUpdate(cut_after_round=1, cut_size=5), organization_id=org_id,
)
player_id = await create_player(org_id, display_name="Spiller")
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
rp_id = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
await _score_holes(tournament_id, round1_id, rp_id, org_id, owner_id, [4] * 18)
await apply_cut(tournament_id, organization_id=org_id)
await update_tournament_participant(
tournament_id, tp_id, TournamentParticipantUpdate(status="dsq"), organization_id=org_id
)
entries = await individual_leaderboard(tournament_id, organization_id=org_id)
entry = next(e for e in entries if e.tournament_participant_id == tp_id)
assert entry.total_label == "DSQ"
assert entry.cut is False # var faktisk innenfor cut-grensen
async def test_status_blocks_scoring_in_any_round_not_just_after_a_point(pool):
"""Strengere enn Cut: en DSQ-et/RTD-et/DNF-et/DNS-et spiller er
blokkert i ALLE runder, ikke bare etter et gitt punkt."""
org_id = await create_org()
owner_id = await create_user()
await add_membership(org_id, owner_id, role="owner")
tournament_id, course_id, tee_id, round1_id, round2_id = await _setup(org_id, owner_id)
player_id = await create_player(org_id, display_name="Spiller")
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
rp1_id = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
rp2_id = await create_tournament_round_participant(org_id, round2_id, tp_id, tee_id)
await update_tournament_participant(
tournament_id, tp_id, TournamentParticipantUpdate(status="dsq"), organization_id=org_id
)
user = CurrentUser(user_id=owner_id)
with pytest.raises(HTTPException) as exc_round1:
await tournament_update_hole(
tournament_id, round1_id, rp1_id, 1,
TournamentHoleUpdate(gross_strokes=4, expected_version=None),
organization_id=org_id, user=user,
)
assert exc_round1.value.status_code == 403
with pytest.raises(HTTPException) as exc_round2:
await tournament_update_hole(
tournament_id, round2_id, rp2_id, 1,
TournamentHoleUpdate(gross_strokes=4, expected_version=None),
organization_id=org_id, user=user,
)
assert exc_round2.value.status_code == 403
async def test_active_status_is_unaffected_default(pool):
"""Ingen regresjon -- default 'active' oppfører seg akkurat som før."""
org_id = await create_org()
owner_id = await create_user()
await add_membership(org_id, owner_id, role="owner")
tournament_id, course_id, tee_id, round1_id, round2_id = await _setup(org_id, owner_id)
player_id = await create_player(org_id, display_name="Spiller")
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
rp_id = await create_tournament_round_participant(org_id, round1_id, tp_id, tee_id)
await _score_holes(tournament_id, round1_id, rp_id, org_id, owner_id, [4] * 18)
entries = await individual_leaderboard(tournament_id, organization_id=org_id)
entry = next(e for e in entries if e.tournament_participant_id == tp_id)
assert entry.status == "active"
assert entry.total_label == "E"
assert entry.position == "1"