tournament_round_hole hadde siden opprettelse (migrasjon 040) kun gross_strokes -- ingen putts/utslag/innspill/bunker/straffeslag-detalj slik frittstående runder (round_hole) allerede har. Migrasjon 075 gir turneringssiden samme feltsett + en ny optimistisk-lås-kolonne (endepunktet var til nå rent siste-skriver-vinner). tournament_participant.stat_level styrer om HoleGrid åpner det uendrede inline-tallfeltet eller det nye HoleStatsSheet. NumberPicker/ChoiceRow/DirectionCross/Stepper trukket ut til en delt hole-stat-inputs.tsx for gjenbruk mellom begge scoringsflytene. Dette er Steg 1 av to mot spillerens per-hull-historikk på tvers av alle runder/turneringer -- se ADR-071/CHANGELOG for full detalj og verifisering. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
"""
|
|
Full statistikkdybde + optimistisk versjonssjekk på tournament_round_hole
|
|
(migrasjon 075, ADR-071) -- speiler test_concurrency_version_check.py sitt
|
|
mønster for round_hole, men gjennom individual_tournaments.py sin egen
|
|
update_hole (self-only autorisasjon, upsert i stedet for ren UPDATE siden
|
|
raden ikke finnes før første score).
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from app.auth import CurrentUser
|
|
from app.routers.individual_tournaments import (
|
|
HoleUpdate,
|
|
TournamentParticipantCreate,
|
|
add_tournament_participant,
|
|
update_hole,
|
|
)
|
|
|
|
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,
|
|
)
|
|
|
|
|
|
async def _setup_tournament(par=4):
|
|
org_id = await create_org()
|
|
user_id = await create_user()
|
|
await add_membership(org_id, user_id, role="owner")
|
|
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 if n == 1 else 4,
|
|
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)
|
|
player_id = await create_player(org_id, user_id=user_id)
|
|
tp_id = await create_tournament_participant(org_id, tournament_id, player_id)
|
|
rp_id = await create_tournament_round_participant(org_id, tournament_round_id, tp_id, tee_id)
|
|
return org_id, user_id, tournament_id, tournament_round_id, rp_id
|
|
|
|
|
|
async def test_update_succeeds_and_increments_version(pool):
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_tournament()
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
out = await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=5, putts=2, expected_version=None),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
assert out.gross_strokes == 5
|
|
assert out.putts == 2
|
|
assert out.version == 1
|
|
|
|
|
|
async def test_stale_version_is_rejected_with_409(pool):
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_tournament()
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
# Første innsending -- raden finnes ikke fra før, INSERT-grenen treffes
|
|
# ubetinget uansett expected_version. Hullet er nå versjon 1.
|
|
await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=4, expected_version=None),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
# "Enhet 1" leser versjon 1, skriver -- lykkes, hullet er nå versjon 2.
|
|
await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=4, expected_version=1),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
|
|
# "Enhet 2" leste OGSÅ versjon 1 (før enhet 1 sin skriving nådde den).
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=6, expected_version=1),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
assert exc_info.value.status_code == 409
|
|
assert exc_info.value.detail["code"] == "STALE_VERSION"
|
|
|
|
|
|
async def test_expected_version_none_bypasses_check(pool):
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_tournament()
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=4, expected_version=None),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=4, expected_version=1),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
# Hullet er nå versjon 2 -- men klienten sender ingen forventet versjon.
|
|
out = await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=7, expected_version=None),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
assert out.gross_strokes == 7
|
|
assert out.version == 3
|
|
|
|
|
|
async def test_first_write_succeeds_regardless_of_expected_version(pool):
|
|
"""Raden finnes ikke før første score -- INSERT-grenen er ubetinget, så
|
|
selv en helt feil expected_version må IKKE blokkere en fersk innsending
|
|
(ulikt round_hole, der raden alltid finnes fra rundestart)."""
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_tournament()
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
out = await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(gross_strokes=5, expected_version=99),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
assert out.gross_strokes == 5
|
|
assert out.version == 1
|
|
|
|
|
|
async def test_full_stat_fields_round_trip(pool):
|
|
org_id, user_id, tournament_id, round_id, rp_id = await _setup_tournament(par=4)
|
|
user = CurrentUser(user_id=user_id)
|
|
|
|
out = await update_hole(
|
|
tournament_id, round_id, rp_id, 1,
|
|
HoleUpdate(
|
|
gross_strokes=4,
|
|
putts=1,
|
|
club_off_tee="Driver",
|
|
tee_shot_result="fairway",
|
|
approach_result="hit",
|
|
chip_count=0,
|
|
bunker_shot_count=1,
|
|
penalty_strokes=0,
|
|
first_putt_distance_bucket="<3m",
|
|
anyway_strokes=0,
|
|
expected_version=None,
|
|
),
|
|
organization_id=org_id, user=user,
|
|
)
|
|
assert out.putts == 1
|
|
assert out.club_off_tee == "Driver"
|
|
assert out.tee_shot_result == "fairway"
|
|
assert out.approach_result == "hit"
|
|
assert out.bunker_shot_count == 1
|
|
assert out.first_putt_distance_bucket == "<3m"
|
|
|
|
|
|
async def test_stat_level_defaults_to_strokes_only_and_is_settable_on_create(pool):
|
|
org_id = await create_org()
|
|
user_id = await create_user()
|
|
await add_membership(org_id, user_id, role="owner")
|
|
tournament_id = await create_tournament(org_id)
|
|
player_id = await create_player(org_id, user_id=user_id)
|
|
|
|
default_out = await add_tournament_participant(
|
|
tournament_id, TournamentParticipantCreate(player_id=player_id),
|
|
organization_id=org_id,
|
|
)
|
|
assert default_out.stat_level == "strokes_only"
|
|
|
|
player_id_2 = await create_player(org_id)
|
|
full_out = await add_tournament_participant(
|
|
tournament_id,
|
|
TournamentParticipantCreate(player_id=player_id_2, stat_level="full"),
|
|
organization_id=org_id,
|
|
)
|
|
assert full_out.stat_level == "full"
|